/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Norris Boyd
 *   Kemal Bayram
 *   Igor Bukanov
 *   Bob Jervis
 *   Roger Lawrence
 *   Andi Vajda
 *   Hannes Wallnoefer
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript.optimizer;

import java.lang.reflect.Constructor;
import java.util.*;

import org.mozilla.classfile.ByteCode;
import org.mozilla.classfile.ClassFileWriter;
import org.mozilla.javascript.*;

class BodyCodegen {
  class FinallyReturnPoint {
    public List<Integer> jsrPoints = new ArrayList<Integer>();
    public int tableLabel = 0;
  }

  private static final int JAVASCRIPT_EXCEPTION = 0;

  private static final int EVALUATOR_EXCEPTION = 1;

  private static final int ECMAERROR_EXCEPTION = 2;

  private static final int THROWABLE_EXCEPTION = 3;

  static final int GENERATOR_TERMINATE = -1;

  static final int GENERATOR_START = 0;

  static final int GENERATOR_YIELD_START = 1;

  private static boolean isArithmeticNode(Node node) {
    int type = node.getType();
    return type == Token.SUB || type == Token.MOD || type == Token.DIV
        || type == Token.MUL;
  }

  ClassFileWriter cfw;

  Codegen codegen;

  CompilerEnvirons compilerEnv;

  ScriptOrFnNode scriptOrFn;

  public int scriptOrFnIndex;

  private int savedCodeOffset;

  private OptFunctionNode fnCurrent;

  private boolean isTopLevel;

  private static final int MAX_LOCALS = 256;

  private int[] locals;

  private short firstFreeLocal;

  private short localsMax;

  private int itsLineNumber;

  private boolean hasVarsInRegs;

  private short[] varRegisters;

  private boolean inDirectCallFunction;

  private boolean itsForcedObjectParameters;

  private int enterAreaStartLabel;

  private int epilogueLabel;

  // special known locals. If you add a new local here, be sure
  // to initialize it to -1 in initBodyGeneration
  private short variableObjectLocal;

  private short popvLocal;

  private short contextLocal;

  private short argsLocal;

  private short operationLocal;

  private short thisObjLocal;

  private short funObjLocal;
  private short itsZeroArgArray;
  private short itsOneArgArray;
  private short scriptRegexpLocal;

  private short generatorStateLocal;

  private boolean isGenerator;

  private int generatorSwitch;

  private int maxLocals = 0;

  private int maxStack = 0;

  private Map<Node, FinallyReturnPoint> finallys;

  private void addDoubleWrap() {
    addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;");
  }

  private void addGoto(Node target, int jumpcode) {
    int targetLabel = getTargetLabel(target);
    cfw.add(jumpcode, targetLabel);
  }

  private void addGotoWithReturn(Node target) {
    FinallyReturnPoint ret = finallys.get(target);
    cfw.addLoadConstant(ret.jsrPoints.size());
    addGoto(target, ByteCode.GOTO);
    int retLabel = cfw.acquireLabel();
    cfw.markLabel(retLabel);
    ret.jsrPoints.add(Integer.valueOf(retLabel));
  }

  /**
   * Generate calls to ScriptRuntime.addInstructionCount to keep track of
   * executed instructions and call <code>observeInstructionCount()</code> if a
   * threshold is exceeded.<br>
   * Calculates the count from getCurrentCodeOffset - savedCodeOffset
   */
  private void addInstructionCount() {
    int count = cfw.getCurrentCodeOffset() - savedCodeOffset;
    if (count == 0)
      return;
    addInstructionCount(count);
  }

  /**
   * Generate calls to ScriptRuntime.addInstructionCount to keep track of
   * executed instructions and call <code>observeInstructionCount()</code> if a
   * threshold is exceeded.<br>
   * Takes the count as a parameter - used to add monitoring to loops and other
   * blocks that don't have any ops - this allows for monitoring/killing of
   * while(true) loops and such.
   */
  private void addInstructionCount(int count) {
    cfw.addALoad(contextLocal);
    cfw.addPush(count);
    addScriptRuntimeInvoke("addInstructionCount",
        "(Lorg/mozilla/javascript/Context;" + "I)V");
  }

  private void addJumpedBooleanWrap(int trueLabel, int falseLabel) {
    cfw.markLabel(falseLabel);
    int skip = cfw.acquireLabel();
    cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE",
        "Ljava/lang/Boolean;");
    cfw.add(ByteCode.GOTO, skip);
    cfw.markLabel(trueLabel);
    cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE",
        "Ljava/lang/Boolean;");
    cfw.markLabel(skip);
    cfw.adjustStackTop(-1); // only have 1 of true/false
  }

  private void addNewObjectArray(int size) {
    if (size == 0) {
      if (itsZeroArgArray >= 0)
        cfw.addALoad(itsZeroArgArray);
      else
        cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/ScriptRuntime",
            "emptyArgs", "[Ljava/lang/Object;");
    } else {
      cfw.addPush(size);
      cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
    }
  }

  private void addObjectToDouble() {
    addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D");
  }

  private void addOptRuntimeInvoke(String methodName, String methodSignature) {
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/optimizer/OptRuntime", methodName,
        methodSignature);
  }

  private void addScriptRuntimeInvoke(String methodName, String methodSignature) {
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org.mozilla.javascript.ScriptRuntime", methodName, methodSignature);
  }

  private void dcpLoadAsNumber(int dcp_register) {
    cfw.addALoad(dcp_register);
    cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
    int isNumberLabel = cfw.acquireLabel();
    cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
    short stack = cfw.getStackTop();
    cfw.addALoad(dcp_register);
    addObjectToDouble();
    int beyond = cfw.acquireLabel();
    cfw.add(ByteCode.GOTO, beyond);
    cfw.markLabel(isNumberLabel, stack);
    cfw.addDLoad(dcp_register + 1);
    cfw.markLabel(beyond);
  }

  private void dcpLoadAsObject(int dcp_register) {
    cfw.addALoad(dcp_register);
    cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
    int isNumberLabel = cfw.acquireLabel();
    cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
    short stack = cfw.getStackTop();
    cfw.addALoad(dcp_register);
    int beyond = cfw.acquireLabel();
    cfw.add(ByteCode.GOTO, beyond);
    cfw.markLabel(isNumberLabel, stack);
    cfw.addDLoad(dcp_register + 1);
    addDoubleWrap();
    cfw.markLabel(beyond);
  }

  // This is a valid call only for a local that is allocated by default.
  private void decReferenceWordLocal(short local) {
    locals[local]--;
  }

  private void generateActivationExit() {
    if (fnCurrent == null || hasVarsInRegs)
      throw Kit.codeBug();
    cfw.addALoad(contextLocal);
    addScriptRuntimeInvoke("exitActivationFunction",
        "(Lorg/mozilla/javascript/Context;)V");
  }

  void generateBodyCode() {
    isGenerator = Codegen.isGenerator(scriptOrFn);

    // generate the body of the current function or script object
    initBodyGeneration();

    if (isGenerator) {

      // All functions in the generated bytecode have a unique name. Every
      // generator has a unique prefix followed by _gen
      String type = "(" + codegen.mainClassSignature
          + "Lorg/mozilla/javascript/Context;"
          + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;"
          + "Ljava/lang/Object;I)Ljava/lang/Object;";
      cfw.startMethod(codegen.getBodyMethodName(scriptOrFn) + "_gen", type,
          (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));
    } else
      cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), codegen
          .getBodyMethodSignature(scriptOrFn),
          (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));

    generatePrologue();
    Node treeTop;
    if (fnCurrent != null)
      treeTop = scriptOrFn.getLastChild();
    else
      treeTop = scriptOrFn;
    generateStatement(treeTop);
    generateEpilogue();

    cfw.stopMethod((short) (localsMax + 1));

    if (isGenerator)
      // generate the user visible method which when invoked will
      // return a generator object
      generateGenerator();
  }

  private void generateCallArgArray(Node node, Node argChild, boolean directCall) {
    int argCount = 0;
    for (Node child = argChild; child != null; child = child.getNext())
      ++argCount;
    // load array object to set arguments
    if (argCount == 1 && itsOneArgArray >= 0)
      cfw.addALoad(itsOneArgArray);
    else
      addNewObjectArray(argCount);
    // Copy arguments into it
    for (int i = 0; i != argCount; ++i) {
      // If we are compiling a generator an argument could be the result
      // of a yield. In that case we will have an immediate on the stack
      // which we need to avoid
      if (!isGenerator) {
        cfw.add(ByteCode.DUP);
        cfw.addPush(i);
      }

      if (!directCall)
        generateExpression(argChild, node);
      else {
        // If this has also been a directCall sequence, the Number
        // flag will have remained set for any parameter so that
        // the values could be copied directly into the outgoing
        // args. Here we want to force it to be treated as not in
        // a Number context, so we set the flag off.
        int dcp_register = nodeIsDirectCallParameter(argChild);
        if (dcp_register >= 0)
          dcpLoadAsObject(dcp_register);
        else {
          generateExpression(argChild, node);
          int childNumberFlag = argChild.getIntProp(Node.ISNUMBER_PROP, -1);
          if (childNumberFlag == Node.BOTH)
            addDoubleWrap();
        }
      }

      // When compiling generators, any argument to a method may be a
      // yield expression. Hence we compile the argument first and then
      // load the argument index and assign the value to the args array.
      if (isGenerator) {
        short tempLocal = getNewWordLocal();
        cfw.addAStore(tempLocal);
        cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;");
        cfw.add(ByteCode.DUP);
        cfw.addPush(i);
        cfw.addALoad(tempLocal);
        releaseWordLocal(tempLocal);
      }

      cfw.add(ByteCode.AASTORE);

      argChild = argChild.getNext();
    }
  }

  private void generateCatchBlock(int exceptionType, short savedVariableObject,
      int catchLabel, int startLabel, int exceptionLocal) {
    int handler = cfw.acquireLabel();
    cfw.markHandler(handler);

    // MS JVM gets cranky if the exception object is left on the stack
    cfw.addAStore(exceptionLocal);

    // reset the variable object local
    cfw.addALoad(savedVariableObject);
    cfw.addAStore(variableObjectLocal);

    String exceptionName;
    if (exceptionType == JAVASCRIPT_EXCEPTION)
      exceptionName = "org/mozilla/javascript/JavaScriptException";
    else if (exceptionType == EVALUATOR_EXCEPTION)
      exceptionName = "org/mozilla/javascript/EvaluatorException";
    else if (exceptionType == ECMAERROR_EXCEPTION)
      exceptionName = "org/mozilla/javascript/EcmaError";
    else if (exceptionType == THROWABLE_EXCEPTION)
      exceptionName = "java/lang/Throwable";
    else
      throw Kit.codeBug();

    // mark the handler
    cfw.addExceptionHandler(startLabel, catchLabel, handler, exceptionName);

    cfw.add(ByteCode.GOTO, catchLabel);
  }

  private void generateCheckForThrowOrClose(int label, boolean hasLocals,
      int nextState) {
    int throwLabel = cfw.acquireLabel();
    int closeLabel = cfw.acquireLabel();

    // throw the user provided object, if the operation is .throw()
    cfw.markLabel(throwLabel);
    cfw.addALoad(argsLocal);
    generateThrowJavaScriptException();

    // throw our special internal exception if the generator is being closed
    cfw.markLabel(closeLabel);
    cfw.addALoad(argsLocal);
    cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
    cfw.add(ByteCode.ATHROW);

    // mark the re-entry point
    // jump here after initializing the locals
    if (label != -1)
      cfw.markLabel(label);
    if (!hasLocals)
      // jump here directly if there are no locals
      cfw.markTableSwitchCase(generatorSwitch, nextState);

    // see if we need to dispatch for .close() or .throw()
    cfw.addILoad(operationLocal);
    cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE);
    cfw.add(ByteCode.IF_ICMPEQ, closeLabel);
    cfw.addILoad(operationLocal);
    cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW);
    cfw.add(ByteCode.IF_ICMPEQ, throwLabel);
  }

  private void generateEpilogue() {
    if (compilerEnv.isGenerateObserverCount())
      addInstructionCount();
    if (isGenerator) {
      // generate locals initialization
      Map<Node, int[]> liveLocals = ((FunctionNode) scriptOrFn).getLiveLocals();
      if (liveLocals != null) {
        ArrayList<Node> nodes = ((FunctionNode) scriptOrFn)
            .getResumptionPoints();
        for (int i = 0; i < nodes.size(); i++) {
          Node node = nodes.get(i);
          int[] live = liveLocals.get(node);
          if (live != null) {
            cfw.markTableSwitchCase(generatorSwitch,
                getNextGeneratorState(node));
            generateGetGeneratorLocalsState();
            for (int j = 0; j < live.length; j++) {
              cfw.add(ByteCode.DUP);
              cfw.addLoadConstant(j);
              cfw.add(ByteCode.AALOAD);
              cfw.addAStore(live[j]);
            }
            cfw.add(ByteCode.POP);
            cfw.add(ByteCode.GOTO, getTargetLabel(node));
          }
        }
      }

      // generate dispatch tables for finally
      if (finallys != null)
        for (Node n : finallys.keySet())
          if (n.getType() == Token.FINALLY) {
            FinallyReturnPoint ret = finallys.get(n);
            // the finally will jump here
            cfw.markLabel(ret.tableLabel, (short) 1);

            // start generating a dispatch table
            int startSwitch = cfw.addTableSwitch(0, ret.jsrPoints.size() - 1);
            int c = 0;
            cfw.markTableSwitchDefault(startSwitch);
            for (int i = 0; i < ret.jsrPoints.size(); i++) {
              // generate gotos back to the JSR location
              cfw.markTableSwitchCase(startSwitch, c);
              cfw.add(ByteCode.GOTO, ret.jsrPoints.get(i).intValue());
              c++;
            }
          }
    }

    if (epilogueLabel != -1)
      cfw.markLabel(epilogueLabel);

    if (hasVarsInRegs) {
      cfw.add(ByteCode.ARETURN);
      return;
    } else if (isGenerator) {
      if (((FunctionNode) scriptOrFn).getResumptionPoints() != null)
        cfw.markTableSwitchDefault(generatorSwitch);

      // change state for re-entry
      generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE);

      // throw StopIteration
      cfw.addALoad(variableObjectLocal);
      addOptRuntimeInvoke("throwStopIteration", "(Ljava/lang/Object;)V");

      Codegen.pushUndefined(cfw);
      cfw.add(ByteCode.ARETURN);

    } else if (fnCurrent == null) {
      cfw.addALoad(popvLocal);
      cfw.add(ByteCode.ARETURN);
    } else {
      generateActivationExit();
      cfw.add(ByteCode.ARETURN);

      // Generate catch block to catch all and rethrow to call exit code
      // under exception propagation as well.

      int finallyHandler = cfw.acquireLabel();
      cfw.markHandler(finallyHandler);
      short exceptionObject = getNewWordLocal();
      cfw.addAStore(exceptionObject);

      // Duplicate generateActivationExit() in the catch block since it
      // takes less space then full-featured ByteCode.JSR/ByteCode.RET
      generateActivationExit();

      cfw.addALoad(exceptionObject);
      releaseWordLocal(exceptionObject);
      // rethrow
      cfw.add(ByteCode.ATHROW);

      // mark the handler
      cfw.addExceptionHandler(enterAreaStartLabel, epilogueLabel,
          finallyHandler, null); // catch any
    }
  }

  private void generateExpression(Node node, Node parent) {
    int type = node.getType();
    Node child = node.getFirstChild();
    switch (type) {
      case Token.USE_STACK :
        break;

      case Token.FUNCTION :
        if (fnCurrent != null || parent.getType() != Token.SCRIPT) {
          int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
          OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
          int t = ofn.fnode.getFunctionType();
          if (t != FunctionNode.FUNCTION_EXPRESSION)
            throw Codegen.badTree();
          visitFunction(ofn, t);
        }
        break;

      case Token.NAME : {
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.addPush(node.getString());
        addScriptRuntimeInvoke("name", "(Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
            + ")Ljava/lang/Object;");
      }
        break;

      case Token.CALL :
      case Token.NEW : {
        int specialType = node.getIntProp(Node.SPECIALCALL_PROP,
            Node.NON_SPECIALCALL);
        if (specialType == Node.NON_SPECIALCALL) {
          OptFunctionNode target;
          target = (OptFunctionNode) node.getProp(Node.DIRECTCALL_PROP);

          if (target != null)
            visitOptimizedCall(node, target, type, child);
          else if (type == Token.CALL)
            visitStandardCall(node, child);
          else
            visitStandardNew(node, child);
        } else
          visitSpecialCall(node, type, specialType, child);
      }
        break;

      case Token.REF_CALL :
        generateFunctionAndThisObj(child, node);
        // stack: ... functionObj thisObj
        child = child.getNext();
        generateCallArgArray(node, child, false);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("callRef", "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;"
            + ")Lorg/mozilla/javascript/Ref;");
        break;

      case Token.NUMBER : {
        double num = node.getDouble();
        if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
          cfw.addPush(num);
        else
          codegen.pushNumberAsObject(cfw, num);
      }
        break;

      case Token.STRING :
        cfw.addPush(node.getString());
        break;

      case Token.THIS :
        cfw.addALoad(thisObjLocal);
        break;

      case Token.THISFN :
        cfw.add(ByteCode.ALOAD_0);
        break;

      case Token.NULL :
        cfw.add(ByteCode.ACONST_NULL);
        break;

      case Token.TRUE :
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE",
            "Ljava/lang/Boolean;");
        break;

      case Token.FALSE :
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE",
            "Ljava/lang/Boolean;");
        break;

      case Token.REGEXP : {
        int i = node.getExistingIntProp(Node.REGEXP_PROP);
        // Scripts can not use REGEXP_ARRAY_FIELD_NAME since
        // it it will make script.exec non-reentrant so they
        // store regexp array in a local variable while
        // functions always access precomputed
        // REGEXP_ARRAY_FIELD_NAME not to consume locals
        if (fnCurrent == null)
          cfw.addALoad(scriptRegexpLocal);
        else {
          cfw.addALoad(funObjLocal);
          cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
              Codegen.REGEXP_ARRAY_FIELD_NAME, Codegen.REGEXP_ARRAY_FIELD_TYPE);
        }
        cfw.addPush(i);
        cfw.add(ByteCode.AALOAD);
      }
        break;

      case Token.COMMA : {
        Node next = child.getNext();
        while (next != null) {
          generateExpression(child, node);
          cfw.add(ByteCode.POP);
          child = next;
          next = next.getNext();
        }
        generateExpression(child, node);
        break;
      }

      case Token.ENUM_NEXT :
      case Token.ENUM_ID : {
        int local = getLocalBlockRegister(node);
        cfw.addALoad(local);
        if (type == Token.ENUM_NEXT)
          addScriptRuntimeInvoke("enumNext",
              "(Ljava/lang/Object;)Ljava/lang/Boolean;");
        else {
          cfw.addALoad(contextLocal);
          addScriptRuntimeInvoke("enumId", "(Ljava/lang/Object;"
              + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        }
        break;
      }

      case Token.ARRAYLIT :
        visitArrayLiteral(node, child);
        break;

      case Token.OBJECTLIT :
        visitObjectLiteral(node, child);
        break;

      case Token.NOT : {
        int trueTarget = cfw.acquireLabel();
        int falseTarget = cfw.acquireLabel();
        int beyond = cfw.acquireLabel();
        generateIfJump(child, node, trueTarget, falseTarget);

        cfw.markLabel(trueTarget);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "FALSE",
            "Ljava/lang/Boolean;");
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(falseTarget);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", "TRUE",
            "Ljava/lang/Boolean;");
        cfw.markLabel(beyond);
        cfw.adjustStackTop(-1);
        break;
      }

      case Token.BITNOT :
        generateExpression(child, node);
        addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
        cfw.addPush(-1); // implement ~a as (a ^ -1)
        cfw.add(ByteCode.IXOR);
        cfw.add(ByteCode.I2D);
        addDoubleWrap();
        break;

      case Token.VOID :
        generateExpression(child, node);
        cfw.add(ByteCode.POP);
        Codegen.pushUndefined(cfw);
        break;

      case Token.TYPEOF :
        generateExpression(child, node);
        addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;"
            + ")Ljava/lang/String;");
        break;

      case Token.TYPEOFNAME :
        visitTypeofname(node);
        break;

      case Token.INC :
      case Token.DEC :
        visitIncDec(node);
        break;

      case Token.OR :
      case Token.AND : {
        generateExpression(child, node);
        cfw.add(ByteCode.DUP);
        addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
        int falseTarget = cfw.acquireLabel();
        if (type == Token.AND)
          cfw.add(ByteCode.IFEQ, falseTarget);
        else
          cfw.add(ByteCode.IFNE, falseTarget);
        cfw.add(ByteCode.POP);
        generateExpression(child.getNext(), node);
        cfw.markLabel(falseTarget);
      }
        break;

      case Token.HOOK : {
        Node ifThen = child.getNext();
        Node ifElse = ifThen.getNext();
        generateExpression(child, node);
        addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
        int elseTarget = cfw.acquireLabel();
        cfw.add(ByteCode.IFEQ, elseTarget);
        short stack = cfw.getStackTop();
        generateExpression(ifThen, node);
        int afterHook = cfw.acquireLabel();
        cfw.add(ByteCode.GOTO, afterHook);
        cfw.markLabel(elseTarget, stack);
        generateExpression(ifElse, node);
        cfw.markLabel(afterHook);
      }
        break;

      case Token.ADD : {
        generateExpression(child, node);
        generateExpression(child.getNext(), node);
        switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) {
          case Node.BOTH :
            cfw.add(ByteCode.DADD);
            break;
          case Node.LEFT :
            addOptRuntimeInvoke("add",
                "(DLjava/lang/Object;)Ljava/lang/Object;");
            break;
          case Node.RIGHT :
            addOptRuntimeInvoke("add",
                "(Ljava/lang/Object;D)Ljava/lang/Object;");
            break;
          default :
            if (child.getType() == Token.STRING)
              addScriptRuntimeInvoke("add", "(Ljava/lang/String;"
                  + "Ljava/lang/Object;" + ")Ljava/lang/String;");
            else if (child.getNext().getType() == Token.STRING)
              addScriptRuntimeInvoke("add", "(Ljava/lang/Object;"
                  + "Ljava/lang/String;" + ")Ljava/lang/String;");
            else {
              cfw.addALoad(contextLocal);
              addScriptRuntimeInvoke("add", "(Ljava/lang/Object;"
                  + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
                  + ")Ljava/lang/Object;");
            }
        }
      }
        break;

      case Token.MUL :
        visitArithmetic(node, ByteCode.DMUL, child, parent);
        break;

      case Token.SUB :
        visitArithmetic(node, ByteCode.DSUB, child, parent);
        break;

      case Token.DIV :
      case Token.MOD :
        visitArithmetic(node,
            type == Token.DIV ? ByteCode.DDIV : ByteCode.DREM, child, parent);
        break;

      case Token.BITOR :
      case Token.BITXOR :
      case Token.BITAND :
      case Token.LSH :
      case Token.RSH :
      case Token.URSH :
        visitBitOp(node, type, child);
        break;

      case Token.POS :
      case Token.NEG :
        generateExpression(child, node);
        addObjectToDouble();
        if (type == Token.NEG)
          cfw.add(ByteCode.DNEG);
        addDoubleWrap();
        break;

      case Token.TO_DOUBLE :
        // cnvt to double (not Double)
        generateExpression(child, node);
        addObjectToDouble();
        break;

      case Token.TO_OBJECT : {
        // convert from double
        int prop = -1;
        if (child.getType() == Token.NUMBER)
          prop = child.getIntProp(Node.ISNUMBER_PROP, -1);
        if (prop != -1) {
          child.removeProp(Node.ISNUMBER_PROP);
          generateExpression(child, node);
          child.putIntProp(Node.ISNUMBER_PROP, prop);
        } else {
          generateExpression(child, node);
          addDoubleWrap();
        }
        break;
      }

      case Token.IN :
      case Token.INSTANCEOF :
      case Token.LE :
      case Token.LT :
      case Token.GE :
      case Token.GT : {
        int trueGOTO = cfw.acquireLabel();
        int falseGOTO = cfw.acquireLabel();
        visitIfJumpRelOp(node, child, trueGOTO, falseGOTO);
        addJumpedBooleanWrap(trueGOTO, falseGOTO);
        break;
      }

      case Token.EQ :
      case Token.NE :
      case Token.SHEQ :
      case Token.SHNE : {
        int trueGOTO = cfw.acquireLabel();
        int falseGOTO = cfw.acquireLabel();
        visitIfJumpEqOp(node, child, trueGOTO, falseGOTO);
        addJumpedBooleanWrap(trueGOTO, falseGOTO);
        break;
      }

      case Token.GETPROP :
      case Token.GETPROPNOWARN :
        visitGetProp(node, child);
        break;

      case Token.GETELEM :
        generateExpression(child, node); // object
        generateExpression(child.getNext(), node); // id
        cfw.addALoad(contextLocal);
        if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
          addScriptRuntimeInvoke("getObjectIndex", "(Ljava/lang/Object;D"
              + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        else {
          cfw.addALoad(variableObjectLocal);
          addScriptRuntimeInvoke("getObjectElem", "(Ljava/lang/Object;"
              + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
              + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;");
        }
        break;

      case Token.GET_REF :
        generateExpression(child, node); // reference
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("refGet", "(Lorg/mozilla/javascript/Ref;"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        break;

      case Token.GETVAR :
        visitGetVar(node);
        break;

      case Token.SETVAR :
        visitSetVar(node, child, true);
        break;

      case Token.SETNAME :
        visitSetName(node, child);
        break;

      case Token.SETCONST :
        visitSetConst(node, child);
        break;

      case Token.SETCONSTVAR :
        visitSetConstVar(node, child, true);
        break;

      case Token.SETPROP :
      case Token.SETPROP_OP :
        visitSetProp(type, node, child);
        break;

      case Token.SETELEM :
      case Token.SETELEM_OP :
        visitSetElem(type, node, child);
        break;

      case Token.SET_REF :
      case Token.SET_REF_OP : {
        generateExpression(child, node);
        child = child.getNext();
        if (type == Token.SET_REF_OP) {
          cfw.add(ByteCode.DUP);
          cfw.addALoad(contextLocal);
          addScriptRuntimeInvoke("refGet", "(Lorg/mozilla/javascript/Ref;"
              + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        }
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("refSet", "(Lorg/mozilla/javascript/Ref;"
            + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
            + ")Ljava/lang/Object;");
      }
        break;

      case Token.DEL_REF :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("refDel", "(Lorg/mozilla/javascript/Ref;"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        break;

      case Token.DELPROP :
        generateExpression(child, node);
        child = child.getNext();
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("delete", "(Ljava/lang/Object;"
            + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
            + ")Ljava/lang/Object;");
        break;

      case Token.BINDNAME : {
        while (child != null) {
          generateExpression(child, node);
          child = child.getNext();
        }
        // Generate code for "ScriptRuntime.bind(varObj, "s")"
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        cfw.addPush(node.getString());
        addScriptRuntimeInvoke("bind", "(Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
            + ")Lorg/mozilla/javascript/Scriptable;");
      }
        break;

      case Token.LOCAL_LOAD :
        cfw.addALoad(getLocalBlockRegister(node));
        break;

      case Token.REF_SPECIAL : {
        String special = (String) node.getProp(Node.NAME_PROP);
        generateExpression(child, node);
        cfw.addPush(special);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("specialRef", "(Ljava/lang/Object;"
            + "Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
            + ")Lorg/mozilla/javascript/Ref;");
      }
        break;

      case Token.REF_MEMBER :
      case Token.REF_NS_MEMBER :
      case Token.REF_NAME :
      case Token.REF_NS_NAME : {
        int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
        // generate possible target, possible namespace and member
        do {
          generateExpression(child, node);
          child = child.getNext();
        } while (child != null);
        cfw.addALoad(contextLocal);
        String methodName, signature;
        switch (type) {
          case Token.REF_MEMBER :
            methodName = "memberRef";
            signature = "(Ljava/lang/Object;" + "Ljava/lang/Object;"
                + "Lorg/mozilla/javascript/Context;" + "I"
                + ")Lorg/mozilla/javascript/Ref;";
            break;
          case Token.REF_NS_MEMBER :
            methodName = "memberRef";
            signature = "(Ljava/lang/Object;" + "Ljava/lang/Object;"
                + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
                + "I" + ")Lorg/mozilla/javascript/Ref;";
            break;
          case Token.REF_NAME :
            methodName = "nameRef";
            signature = "(Ljava/lang/Object;"
                + "Lorg/mozilla/javascript/Context;"
                + "Lorg/mozilla/javascript/Scriptable;" + "I"
                + ")Lorg/mozilla/javascript/Ref;";
            cfw.addALoad(variableObjectLocal);
            break;
          case Token.REF_NS_NAME :
            methodName = "nameRef";
            signature = "(Ljava/lang/Object;"
                + "Lorg/mozilla/javascript/Context;"
                + "Lorg/mozilla/javascript/Scriptable;" + "I"
                + ")Lorg/mozilla/javascript/Ref;";
            cfw.addALoad(variableObjectLocal);
            break;
          default :
            throw Kit.codeBug();
        }
        cfw.addPush(memberTypeFlags);
        addScriptRuntimeInvoke(methodName, signature);
      }
        break;

      case Token.DOTQUERY :
        visitDotQuery(node, child);
        break;

      case Token.ESCXMLATTR :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("escapeAttributeValue", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/String;");
        break;

      case Token.ESCXMLTEXT :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("escapeTextValue", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/String;");
        break;

      case Token.DEFAULTNAMESPACE :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("setDefaultNamespace", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
        break;

      case Token.YIELD :
        generateYieldPoint(node, true);
        break;

      case Token.WITHEXPR : {
        Node enterWith = child;
        Node with = enterWith.getNext();
        Node leaveWith = with.getNext();
        generateStatement(enterWith);
        generateExpression(with.getFirstChild(), with);
        generateStatement(leaveWith);
        break;
      }

      case Token.ARRAYCOMP : {
        Node initStmt = child;
        Node expr = child.getNext();
        generateStatement(initStmt);
        generateExpression(expr, node);
        break;
      }

      default :
        throw new RuntimeException("Unexpected node type " + type);
    }

  }

  private void generateFunctionAndThisObj(Node node, Node parent) {
    // Place on stack (function object, function this) pair
    int type = node.getType();
    switch (node.getType()) {
      case Token.GETPROPNOWARN :
        throw Kit.codeBug();

      case Token.GETPROP :
      case Token.GETELEM : {
        Node target = node.getFirstChild();
        generateExpression(target, node);
        Node id = target.getNext();
        if (type == Token.GETPROP) {
          String property = id.getString();
          cfw.addPush(property);
          cfw.addALoad(contextLocal);
          cfw.addALoad(variableObjectLocal);
          addScriptRuntimeInvoke("getPropFunctionAndThis",
              "(Ljava/lang/Object;" + "Ljava/lang/String;"
                  + "Lorg/mozilla/javascript/Context;"
                  + "Lorg/mozilla/javascript/Scriptable;"
                  + ")Lorg/mozilla/javascript/Callable;");
        } else {
          // Optimizer do not optimize this case for now
          if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
            throw Codegen.badTree();
          generateExpression(id, node); // id
          cfw.addALoad(contextLocal);
          addScriptRuntimeInvoke("getElemFunctionAndThis",
              "(Ljava/lang/Object;" + "Ljava/lang/Object;"
                  + "Lorg/mozilla/javascript/Context;"
                  + ")Lorg/mozilla/javascript/Callable;");
        }
        break;
      }

      case Token.NAME : {
        String name = node.getString();
        cfw.addPush(name);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke("getNameFunctionAndThis", "(Ljava/lang/String;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + ")Lorg/mozilla/javascript/Callable;");
        break;
      }

      default : // including GETVAR
        generateExpression(node, parent);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("getValueFunctionAndThis", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;"
            + ")Lorg/mozilla/javascript/Callable;");
        break;
    }
    // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis
    cfw.addALoad(contextLocal);
    addScriptRuntimeInvoke("lastStoredScriptable",
        "(Lorg/mozilla/javascript/Context;"
            + ")Lorg/mozilla/javascript/Scriptable;");
  }

  // This creates a the user-facing function that returns a NativeGenerator
  // object.
  private void generateGenerator() {
    cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), codegen
        .getBodyMethodSignature(scriptOrFn),
        (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));

    initBodyGeneration();
    argsLocal = firstFreeLocal++;
    localsMax = firstFreeLocal;

    // get top level scope
    if (fnCurrent != null
        && !inDirectCallFunction
        && (!compilerEnv.isUseDynamicScope() || fnCurrent.fnode
            .getIgnoreDynamicScope())) {
      // Unless we're either in a direct call or using dynamic scope,
      // use the enclosing scope of the function as our variable object.
      cfw.addALoad(funObjLocal);
      cfw.addInvoke(ByteCode.INVOKEINTERFACE,
          "org/mozilla/javascript/Scriptable", "getParentScope",
          "()Lorg/mozilla/javascript/Scriptable;");
      cfw.addAStore(variableObjectLocal);
    }

    // generators are forced to have an activation record
    cfw.addALoad(funObjLocal);
    cfw.addALoad(variableObjectLocal);
    cfw.addALoad(argsLocal);
    addScriptRuntimeInvoke("createFunctionActivation",
        "(Lorg/mozilla/javascript/NativeFunction;"
            + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
            + ")Lorg/mozilla/javascript/Scriptable;");
    cfw.addAStore(variableObjectLocal);

    // create a function object
    cfw.add(ByteCode.NEW, codegen.mainClassName);
    // Call function constructor
    cfw.add(ByteCode.DUP);
    cfw.addALoad(variableObjectLocal);
    cfw.addALoad(contextLocal); // load 'cx'
    cfw.addPush(scriptOrFnIndex);
    cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, "<init>",
        Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);

    // Init mainScript field
    cfw.add(ByteCode.DUP);
    if (isTopLevel)
      Kit.codeBug(); // Only functions can be generators
    cfw.add(ByteCode.ALOAD_0);
    cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
        Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);
    cfw.add(ByteCode.PUTFIELD, codegen.mainClassName,
        Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);

    generateNestedFunctionInits();

    // create the NativeGenerator object that we return
    cfw.addALoad(variableObjectLocal);
    cfw.addALoad(thisObjLocal);
    cfw.addLoadConstant(maxLocals);
    cfw.addLoadConstant(maxStack);
    addOptRuntimeInvoke("createNativeGenerator",
        "(Lorg/mozilla/javascript/NativeFunction;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + "Lorg/mozilla/javascript/Scriptable;II"
            + ")Lorg/mozilla/javascript/Scriptable;");

    cfw.add(ByteCode.ARETURN);
    cfw.stopMethod((short) (localsMax + 1));
  }

  private void generateGetGeneratorLocalsState() {
    cfw.addALoad(generatorStateLocal);
    addOptRuntimeInvoke("getGeneratorLocalsState",
        "(Ljava/lang/Object;)[Ljava/lang/Object;");
  }

  private void generateGetGeneratorResumptionPoint() {
    cfw.addALoad(generatorStateLocal);
    cfw.add(ByteCode.GETFIELD, OptRuntime.GeneratorState.CLASS_NAME,
        OptRuntime.GeneratorState.resumptionPoint_NAME,
        OptRuntime.GeneratorState.resumptionPoint_TYPE);
  }

  private void generateGetGeneratorStackState() {
    cfw.addALoad(generatorStateLocal);
    addOptRuntimeInvoke("getGeneratorStackState",
        "(Ljava/lang/Object;)[Ljava/lang/Object;");
  }

  private void generateIfJump(Node node, Node parent, int trueLabel,
      int falseLabel) {
    // System.out.println("gen code for " + node.toString());

    int type = node.getType();
    Node child = node.getFirstChild();

    switch (type) {
      case Token.NOT :
        generateIfJump(child, node, falseLabel, trueLabel);
        break;

      case Token.OR :
      case Token.AND : {
        int interLabel = cfw.acquireLabel();
        if (type == Token.AND)
          generateIfJump(child, node, interLabel, falseLabel);
        else
          generateIfJump(child, node, trueLabel, interLabel);
        cfw.markLabel(interLabel);
        child = child.getNext();
        generateIfJump(child, node, trueLabel, falseLabel);
        break;
      }

      case Token.IN :
      case Token.INSTANCEOF :
      case Token.LE :
      case Token.LT :
      case Token.GE :
      case Token.GT :
        visitIfJumpRelOp(node, child, trueLabel, falseLabel);
        break;

      case Token.EQ :
      case Token.NE :
      case Token.SHEQ :
      case Token.SHNE :
        visitIfJumpEqOp(node, child, trueLabel, falseLabel);
        break;

      default :
        // Generate generic code for non-optimized jump
        generateExpression(node, parent);
        addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
        cfw.add(ByteCode.IFNE, trueLabel);
        cfw.add(ByteCode.GOTO, falseLabel);
    }
  }

  private void generateIntegerUnwrap() {
    cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer", "intValue",
        "()I");
  }

  private void generateIntegerWrap() {
    cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf",
        "(I)Ljava/lang/Integer;");
  }

  private void generateNestedFunctionInits() {
    int functionCount = scriptOrFn.getFunctionCount();
    for (int i = 0; i != functionCount; i++) {
      OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i);
      if (ofn.fnode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT)
        visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT);
    }
  }

  /**
   * Generate the prologue for a function or script.
   */
  private void generatePrologue() {
    if (inDirectCallFunction) {
      int directParameterCount = scriptOrFn.getParamCount();
      // 0 is reserved for function Object 'this'
      // 1 is reserved for context
      // 2 is reserved for parentScope
      // 3 is reserved for script 'this'
      if (firstFreeLocal != 4)
        Kit.codeBug();
      for (int i = 0; i != directParameterCount; ++i) {
        varRegisters[i] = firstFreeLocal;
        // 3 is 1 for Object parm and 2 for double parm
        firstFreeLocal += 3;
      }
      if (!fnCurrent.getParameterNumberContext()) {
        // make sure that all parameters are objects
        itsForcedObjectParameters = true;
        for (int i = 0; i != directParameterCount; ++i) {
          short reg = varRegisters[i];
          cfw.addALoad(reg);
          cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
              "Ljava/lang/Class;");
          int isObjectLabel = cfw.acquireLabel();
          cfw.add(ByteCode.IF_ACMPNE, isObjectLabel);
          cfw.addDLoad(reg + 1);
          addDoubleWrap();
          cfw.addAStore(reg);
          cfw.markLabel(isObjectLabel);
        }
      }
    }

    if (fnCurrent != null
        && !inDirectCallFunction
        && (!compilerEnv.isUseDynamicScope() || fnCurrent.fnode
            .getIgnoreDynamicScope())) {
      // Unless we're either in a direct call or using dynamic scope,
      // use the enclosing scope of the function as our variable object.
      cfw.addALoad(funObjLocal);
      cfw.addInvoke(ByteCode.INVOKEINTERFACE,
          "org/mozilla/javascript/Scriptable", "getParentScope",
          "()Lorg/mozilla/javascript/Scriptable;");
      cfw.addAStore(variableObjectLocal);
    }

    // reserve 'args[]'
    argsLocal = firstFreeLocal++;
    localsMax = firstFreeLocal;

    // Generate Generator specific prelude
    if (isGenerator) {

      // reserve 'args[]'
      operationLocal = firstFreeLocal++;
      localsMax = firstFreeLocal;

      // Local 3 is a reference to a GeneratorState object. The rest
      // of codegen expects local 3 to be a reference to the thisObj.
      // So move the value in local 3 to generatorStateLocal, and load
      // the saved thisObj from the GeneratorState object.
      cfw.addALoad(thisObjLocal);
      generatorStateLocal = firstFreeLocal++;
      localsMax = firstFreeLocal;
      cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME);
      cfw.add(ByteCode.DUP);
      cfw.addAStore(generatorStateLocal);
      cfw.add(ByteCode.GETFIELD, OptRuntime.GeneratorState.CLASS_NAME,
          OptRuntime.GeneratorState.thisObj_NAME,
          OptRuntime.GeneratorState.thisObj_TYPE);
      cfw.addAStore(thisObjLocal);

      if (epilogueLabel == -1)
        epilogueLabel = cfw.acquireLabel();

      ArrayList<Node> targets = ((FunctionNode) scriptOrFn)
          .getResumptionPoints();
      if (targets != null) {
        // get resumption point
        generateGetGeneratorResumptionPoint();

        // generate dispatch table
        generatorSwitch = cfw.addTableSwitch(0, targets.size()
            + GENERATOR_START);
        generateCheckForThrowOrClose(-1, false, GENERATOR_START);
      }
    }

    if (fnCurrent == null)
      // See comments in case Token.REGEXP
      if (scriptOrFn.getRegexpCount() != 0) {
        scriptRegexpLocal = getNewWordLocal();
        codegen.pushRegExpArray(cfw, scriptOrFn, contextLocal,
            variableObjectLocal);
        cfw.addAStore(scriptRegexpLocal);
      }

    if (compilerEnv.isGenerateObserverCount())
      saveCurrentCodeOffset();

    if (hasVarsInRegs) {
      // No need to create activation. Pad arguments if need be.
      int parmCount = scriptOrFn.getParamCount();
      if (parmCount > 0 && !inDirectCallFunction) {
        // Set up args array
        // check length of arguments, pad if need be
        cfw.addALoad(argsLocal);
        cfw.add(ByteCode.ARRAYLENGTH);
        cfw.addPush(parmCount);
        int label = cfw.acquireLabel();
        cfw.add(ByteCode.IF_ICMPGE, label);
        cfw.addALoad(argsLocal);
        cfw.addPush(parmCount);
        addScriptRuntimeInvoke("padArguments", "([Ljava/lang/Object;I"
            + ")[Ljava/lang/Object;");
        cfw.addAStore(argsLocal);
        cfw.markLabel(label);
      }

      int paramCount = fnCurrent.fnode.getParamCount();
      int varCount = fnCurrent.fnode.getParamAndVarCount();
      boolean[] constDeclarations = fnCurrent.fnode.getParamAndVarConst();

      // REMIND - only need to initialize the vars that don't get a value
      // before the next call and are used in the function
      short firstUndefVar = -1;
      for (int i = 0; i != varCount; ++i) {
        short reg = -1;
        if (i < paramCount) {
          if (!inDirectCallFunction) {
            reg = getNewWordLocal();
            cfw.addALoad(argsLocal);
            cfw.addPush(i);
            cfw.add(ByteCode.AALOAD);
            cfw.addAStore(reg);
          }
        } else if (fnCurrent.isNumberVar(i)) {
          reg = getNewWordPairLocal(constDeclarations[i]);
          cfw.addPush(0.0);
          cfw.addDStore(reg);
        } else {
          reg = getNewWordLocal(constDeclarations[i]);
          if (firstUndefVar == -1) {
            Codegen.pushUndefined(cfw);
            firstUndefVar = reg;
          } else
            cfw.addALoad(firstUndefVar);
          cfw.addAStore(reg);
        }
        if (reg >= 0) {
          if (constDeclarations[i]) {
            cfw.addPush(0);
            cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1));
          }
          varRegisters[i] = reg;
        }

        // Add debug table entry if we're generating debug info
        if (compilerEnv.isGenerateDebugInfo()) {
          String name = fnCurrent.fnode.getParamOrVarName(i);
          String type = fnCurrent.isNumberVar(i) ? "D" : "Ljava/lang/Object;";
          int startPC = cfw.getCurrentCodeOffset();
          if (reg < 0)
            reg = varRegisters[i];
          cfw.addVariableDescriptor(name, type, startPC, reg);
        }
      }

      // Skip creating activation object.
      return;
    }

    // skip creating activation object for the body of a generator. The
    // activation record required by a generator has already been created
    // in generateGenerator().
    if (isGenerator)
      return;

    String debugVariableName;
    if (fnCurrent != null) {
      debugVariableName = "activation";
      cfw.addALoad(funObjLocal);
      cfw.addALoad(variableObjectLocal);
      cfw.addALoad(argsLocal);
      addScriptRuntimeInvoke("createFunctionActivation",
          "(Lorg/mozilla/javascript/NativeFunction;"
              + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
              + ")Lorg/mozilla/javascript/Scriptable;");
      cfw.addAStore(variableObjectLocal);
      cfw.addALoad(contextLocal);
      cfw.addALoad(variableObjectLocal);
      addScriptRuntimeInvoke("enterActivationFunction",
          "(Lorg/mozilla/javascript/Context;"
              + "Lorg/mozilla/javascript/Scriptable;" + ")V");
    } else {
      debugVariableName = "global";
      cfw.addALoad(funObjLocal);
      cfw.addALoad(thisObjLocal);
      cfw.addALoad(contextLocal);
      cfw.addALoad(variableObjectLocal);
      cfw.addPush(0); // false to indicate it is not eval script
      addScriptRuntimeInvoke("initScript",
          "(Lorg/mozilla/javascript/NativeFunction;"
              + "Lorg/mozilla/javascript/Scriptable;"
              + "Lorg/mozilla/javascript/Context;"
              + "Lorg/mozilla/javascript/Scriptable;" + "Z" + ")V");
    }

    enterAreaStartLabel = cfw.acquireLabel();
    epilogueLabel = cfw.acquireLabel();
    cfw.markLabel(enterAreaStartLabel);

    generateNestedFunctionInits();

    // default is to generate debug info
    if (compilerEnv.isGenerateDebugInfo())
      cfw.addVariableDescriptor(debugVariableName,
          "Lorg/mozilla/javascript/Scriptable;", cfw.getCurrentCodeOffset(),
          variableObjectLocal);

    if (fnCurrent == null) {
      // OPT: use dataflow to prove that this assignment is dead
      popvLocal = getNewWordLocal();
      Codegen.pushUndefined(cfw);
      cfw.addAStore(popvLocal);

      int linenum = scriptOrFn.getEndLineno();
      if (linenum != -1)
        cfw.addLineNumberEntry((short) linenum);

    } else {
      if (fnCurrent.itsContainsCalls0) {
        itsZeroArgArray = getNewWordLocal();
        cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/ScriptRuntime",
            "emptyArgs", "[Ljava/lang/Object;");
        cfw.addAStore(itsZeroArgArray);
      }
      if (fnCurrent.itsContainsCalls1) {
        itsOneArgArray = getNewWordLocal();
        cfw.addPush(1);
        cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
        cfw.addAStore(itsOneArgArray);
      }
    }
  }

  private boolean generateSaveLocals(Node node) {
    int count = 0;
    for (int i = 0; i < firstFreeLocal; i++)
      if (locals[i] != 0)
        count++;

    if (count == 0) {
      ((FunctionNode) scriptOrFn).addLiveLocals(node, null);
      return false;
    }

    // calculate the max locals
    maxLocals = maxLocals > count ? maxLocals : count;

    // create a locals list
    int[] ls = new int[count];
    int s = 0;
    for (int i = 0; i < firstFreeLocal; i++)
      if (locals[i] != 0) {
        ls[s] = i;
        s++;
      }

    // save the locals
    ((FunctionNode) scriptOrFn).addLiveLocals(node, ls);

    // save locals
    generateGetGeneratorLocalsState();
    for (int i = 0; i < count; i++) {
      cfw.add(ByteCode.DUP);
      cfw.addLoadConstant(i);
      cfw.addALoad(ls[i]);
      cfw.add(ByteCode.AASTORE);
    }
    // pop the array off the stack
    cfw.add(ByteCode.POP);

    return true;
  }

  private void generateSetGeneratorResumptionPoint(int nextState) {
    cfw.addALoad(generatorStateLocal);
    cfw.addLoadConstant(nextState);
    cfw.add(ByteCode.PUTFIELD, OptRuntime.GeneratorState.CLASS_NAME,
        OptRuntime.GeneratorState.resumptionPoint_NAME,
        OptRuntime.GeneratorState.resumptionPoint_TYPE);
  }

  private void generateStatement(Node node) {
    updateLineNumber(node);
    int type = node.getType();
    Node child = node.getFirstChild();
    switch (type) {
      case Token.LOOP :
      case Token.LABEL :
      case Token.WITH :
      case Token.SCRIPT :
      case Token.BLOCK :
      case Token.EMPTY :
        // no-ops.
        if (compilerEnv.isGenerateObserverCount())
          // Need to add instruction count even for no-ops to catch
          // cases like while (1) {}
          addInstructionCount(1);
        while (child != null) {
          generateStatement(child);
          child = child.getNext();
        }
        break;

      case Token.LOCAL_BLOCK : {
        int local = getNewWordLocal();
        if (isGenerator) {
          cfw.add(ByteCode.ACONST_NULL);
          cfw.addAStore(local);
        }
        node.putIntProp(Node.LOCAL_PROP, local);
        while (child != null) {
          generateStatement(child);
          child = child.getNext();
        }
        releaseWordLocal((short) local);
        node.removeProp(Node.LOCAL_PROP);
        break;
      }

      case Token.FUNCTION : {
        int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
        OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
        int t = ofn.fnode.getFunctionType();
        if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT)
          visitFunction(ofn, t);
        else if (t != FunctionNode.FUNCTION_STATEMENT)
          throw Codegen.badTree();
        break;
      }

      case Token.TRY :
        visitTryCatchFinally((Node.Jump) node, child);
        break;

      case Token.CATCH_SCOPE : {
        // nothing stays on the stack on entry into a catch scope
        cfw.setStackTop((short) 0);

        int local = getLocalBlockRegister(node);
        int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);

        String name = child.getString(); // name of exception
        child = child.getNext();
        generateExpression(child, node); // load expression object
        if (scopeIndex == 0)
          cfw.add(ByteCode.ACONST_NULL);
        else
          // Load previous catch scope object
          cfw.addALoad(local);
        cfw.addPush(name);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);

        addScriptRuntimeInvoke("newCatchScope", "(Ljava/lang/Throwable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(local);
      }
        break;

      case Token.THROW :
        generateExpression(child, node);
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        generateThrowJavaScriptException();
        break;

      case Token.RETHROW :
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        cfw.addALoad(getLocalBlockRegister(node));
        cfw.add(ByteCode.ATHROW);
        break;

      case Token.RETURN_RESULT :
      case Token.RETURN :
        if (!isGenerator)
          if (child != null)
            generateExpression(child, node);
          else if (type == Token.RETURN)
            Codegen.pushUndefined(cfw);
          else {
            if (popvLocal < 0)
              throw Codegen.badTree();
            cfw.addALoad(popvLocal);
          }
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        if (epilogueLabel == -1) {
          if (!hasVarsInRegs)
            throw Codegen.badTree();
          epilogueLabel = cfw.acquireLabel();
        }
        cfw.add(ByteCode.GOTO, epilogueLabel);
        break;

      case Token.SWITCH :
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        visitSwitch((Node.Jump) node, child);
        break;

      case Token.ENTERWITH :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke("enterWith", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(variableObjectLocal);
        incReferenceWordLocal(variableObjectLocal);
        break;

      case Token.LEAVEWITH :
        cfw.addALoad(variableObjectLocal);
        addScriptRuntimeInvoke("leaveWith",
            "(Lorg/mozilla/javascript/Scriptable;"
                + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(variableObjectLocal);
        decReferenceWordLocal(variableObjectLocal);
        break;

      case Token.ENUM_INIT_KEYS :
      case Token.ENUM_INIT_VALUES :
      case Token.ENUM_INIT_ARRAY :
        generateExpression(child, node);
        cfw.addALoad(contextLocal);
        int enumType = type == Token.ENUM_INIT_KEYS
            ? ScriptRuntime.ENUMERATE_KEYS
            : type == Token.ENUM_INIT_VALUES
                ? ScriptRuntime.ENUMERATE_VALUES
                : ScriptRuntime.ENUMERATE_ARRAY;
        cfw.addPush(enumType);
        addScriptRuntimeInvoke("enumInit", "(Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;" + "I" + ")Ljava/lang/Object;");
        cfw.addAStore(getLocalBlockRegister(node));
        break;

      case Token.EXPR_VOID :
        if (child.getType() == Token.SETVAR)
          /*
           * special case this so as to avoid unnecessary load's & pop's
           */
          visitSetVar(child, child.getFirstChild(), false);
        else if (child.getType() == Token.SETCONSTVAR)
          /*
           * special case this so as to avoid unnecessary load's & pop's
           */
          visitSetConstVar(child, child.getFirstChild(), false);
        else if (child.getType() == Token.YIELD)
          generateYieldPoint(child, false);
        else {
          generateExpression(child, node);
          if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
            cfw.add(ByteCode.POP2);
          else
            cfw.add(ByteCode.POP);
        }
        break;

      case Token.EXPR_RESULT :
        generateExpression(child, node);
        if (popvLocal < 0)
          popvLocal = getNewWordLocal();
        cfw.addAStore(popvLocal);
        break;

      case Token.TARGET : {
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        int label = getTargetLabel(node);
        cfw.markLabel(label);
        if (compilerEnv.isGenerateObserverCount())
          saveCurrentCodeOffset();
      }
        break;

      case Token.JSR :
      case Token.GOTO :
      case Token.IFEQ :
      case Token.IFNE :
        if (compilerEnv.isGenerateObserverCount())
          addInstructionCount();
        visitGoto((Node.Jump) node, type, child);
        break;

      case Token.FINALLY : {
        if (compilerEnv.isGenerateObserverCount())
          saveCurrentCodeOffset();
        // there is exactly one value on the stack when enterring
        // finally blocks: the return address (or its int encoding)
        cfw.setStackTop((short) 1);

        // Save return address in a new local
        int finallyRegister = getNewWordLocal();
        if (isGenerator)
          generateIntegerWrap();
        cfw.addAStore(finallyRegister);

        while (child != null) {
          generateStatement(child);
          child = child.getNext();
        }
        if (isGenerator) {
          cfw.addALoad(finallyRegister);
          cfw.add(ByteCode.CHECKCAST, "java/lang/Integer");
          generateIntegerUnwrap();
          FinallyReturnPoint ret = finallys.get(node);
          ret.tableLabel = cfw.acquireLabel();
          cfw.add(ByteCode.GOTO, ret.tableLabel);
        } else
          cfw.add(ByteCode.RET, finallyRegister);
        releaseWordLocal((short) finallyRegister);
      }
        break;

      case Token.DEBUGGER :
        break;

      default :
        throw Codegen.badTree();
    }

  }

  private void generateThrowJavaScriptException() {
    cfw.add(ByteCode.NEW, "org/mozilla/javascript/JavaScriptException");
    cfw.add(ByteCode.DUP_X1);
    cfw.add(ByteCode.SWAP);
    cfw.addPush(scriptOrFn.getSourceName());
    cfw.addPush(itsLineNumber);
    cfw.addInvoke(ByteCode.INVOKESPECIAL,
        "org/mozilla/javascript/JavaScriptException", "<init>",
        "(Ljava/lang/Object;Ljava/lang/String;I)V");
    cfw.add(ByteCode.ATHROW);
  }

  private void generateYieldPoint(Node node, boolean exprContext) {
    // save stack state
    int top = cfw.getStackTop();
    maxStack = maxStack > top ? maxStack : top;
    if (cfw.getStackTop() != 0) {
      generateGetGeneratorStackState();
      for (int i = 0; i < top; i++) {
        cfw.add(ByteCode.DUP_X1);
        cfw.add(ByteCode.SWAP);
        cfw.addLoadConstant(i);
        cfw.add(ByteCode.SWAP);
        cfw.add(ByteCode.AASTORE);
      }
      // pop the array object
      cfw.add(ByteCode.POP);
    }

    // generate the yield argument
    Node child = node.getFirstChild();
    if (child != null)
      generateExpression(child, node);
    else
      Codegen.pushUndefined(cfw);

    // change the resumption state
    int nextState = getNextGeneratorState(node);
    generateSetGeneratorResumptionPoint(nextState);

    boolean hasLocals = generateSaveLocals(node);

    cfw.add(ByteCode.ARETURN);

    generateCheckForThrowOrClose(getTargetLabel(node), hasLocals, nextState);

    // reconstruct the stack
    if (top != 0) {
      generateGetGeneratorStackState();
      for (int i = 0; i < top; i++) {
        cfw.add(ByteCode.DUP);
        cfw.addLoadConstant(top - i - 1);
        cfw.add(ByteCode.AALOAD);
        cfw.add(ByteCode.SWAP);
      }
      cfw.add(ByteCode.POP);
    }

    // load return value from yield
    if (exprContext)
      cfw.addALoad(argsLocal);
  }

  private void genSimpleCompare(int type, int trueGOTO, int falseGOTO) {
    if (trueGOTO == -1)
      throw Codegen.badTree();
    switch (type) {
      case Token.LE :
        cfw.add(ByteCode.DCMPG);
        cfw.add(ByteCode.IFLE, trueGOTO);
        break;
      case Token.GE :
        cfw.add(ByteCode.DCMPL);
        cfw.add(ByteCode.IFGE, trueGOTO);
        break;
      case Token.LT :
        cfw.add(ByteCode.DCMPG);
        cfw.add(ByteCode.IFLT, trueGOTO);
        break;
      case Token.GT :
        cfw.add(ByteCode.DCMPL);
        cfw.add(ByteCode.IFGT, trueGOTO);
        break;
      default :
        throw Codegen.badTree();

    }
    if (falseGOTO != -1)
      cfw.add(ByteCode.GOTO, falseGOTO);
  }

  private short getConsecutiveSlots(int count, boolean isConst) {
    if (isConst)
      count++;
    short result = firstFreeLocal;
    while (true) {
      if (result >= MAX_LOCALS - 1)
        break;
      int i;
      for (i = 0; i < count; i++)
        if (locals[result + i] != 0)
          break;
      if (i >= count)
        break;
      result++;
    }
    return result;
  }
  private int getLocalBlockRegister(Node node) {
    Node localBlock = (Node) node.getProp(Node.LOCAL_BLOCK_PROP);
    int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP);
    return localSlot;
  }
  private short getNewWordLocal() {
    short result = firstFreeLocal;
    locals[result] = 1;
    for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++)
      if (locals[i] == 0) {
        firstFreeLocal = (short) i;
        if (localsMax < firstFreeLocal)
          localsMax = firstFreeLocal;
        return result;
      }
    throw Context
        .reportRuntimeError("Program too complex " + "(out of locals)");
  }

  private short getNewWordLocal(boolean isConst) {
    short result = getConsecutiveSlots(1, isConst);
    if (result < MAX_LOCALS - 1) {
      locals[result] = 1;
      if (isConst)
        locals[result + 1] = 1;
      if (result == firstFreeLocal) {
        for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++)
          if (locals[i] == 0) {
            firstFreeLocal = (short) i;
            if (localsMax < firstFreeLocal)
              localsMax = firstFreeLocal;
            return result;
          }
      } else
        return result;
    }
    throw Context
        .reportRuntimeError("Program too complex " + "(out of locals)");
  }
  /**
   * Const locals use an extra slot to hold the has-been-assigned-once flag at
   * runtime.
   * 
   * @param isConst
   *          true iff the variable is const
   * @return the register for the word pair (double/long)
   */
  private short getNewWordPairLocal(boolean isConst) {
    short result = getConsecutiveSlots(2, isConst);
    if (result < MAX_LOCALS - 1) {
      locals[result] = 1;
      locals[result + 1] = 1;
      if (isConst)
        locals[result + 2] = 1;
      if (result == firstFreeLocal) {
        for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++)
          if (locals[i] == 0) {
            firstFreeLocal = (short) i;
            if (localsMax < firstFreeLocal)
              localsMax = firstFreeLocal;
            return result;
          }
      } else
        return result;
    }
    throw Context
        .reportRuntimeError("Program too complex " + "(out of locals)");
  }
  private int getNextGeneratorState(Node node) {
    int nodeIndex = ((FunctionNode) scriptOrFn).getResumptionPoints().indexOf(
        node);
    return nodeIndex + GENERATOR_YIELD_START;
  }
  private int getTargetLabel(Node target) {
    int labelId = target.labelId();
    if (labelId == -1) {
      labelId = cfw.acquireLabel();
      target.labelId(labelId);
    }
    return labelId;
  }
  // This is a valid call only for a local that is allocated by default.
  private void incReferenceWordLocal(short local) {
    locals[local]++;
  }
  private void initBodyGeneration() {
    isTopLevel = scriptOrFn == codegen.scriptOrFnNodes[0];

    varRegisters = null;
    if (scriptOrFn.getType() == Token.FUNCTION) {
      fnCurrent = OptFunctionNode.get(scriptOrFn);
      hasVarsInRegs = !fnCurrent.fnode.requiresActivation();
      if (hasVarsInRegs) {
        int n = fnCurrent.fnode.getParamAndVarCount();
        if (n != 0)
          varRegisters = new short[n];
      }
      inDirectCallFunction = fnCurrent.isTargetOfDirectCall();
      if (inDirectCallFunction && !hasVarsInRegs)
        Codegen.badTree();
    } else {
      fnCurrent = null;
      hasVarsInRegs = false;
      inDirectCallFunction = false;
    }

    locals = new int[MAX_LOCALS];

    funObjLocal = 0;
    contextLocal = 1;
    variableObjectLocal = 2;
    thisObjLocal = 3;
    localsMax = (short) 4; // number of parms + "this"
    firstFreeLocal = 4;

    popvLocal = -1;
    argsLocal = -1;
    itsZeroArgArray = -1;
    itsOneArgArray = -1;
    scriptRegexpLocal = -1;
    epilogueLabel = -1;
    enterAreaStartLabel = -1;
    generatorStateLocal = -1;
  }

  private int nodeIsDirectCallParameter(Node node) {
    if (node.getType() == Token.GETVAR && inDirectCallFunction
        && !itsForcedObjectParameters) {
      int varIndex = fnCurrent.getVarIndex(node);
      if (fnCurrent.isParameter(varIndex))
        return varRegisters[varIndex];
    }
    return -1;
  }
  private void releaseWordLocal(short local) {
    if (local < firstFreeLocal)
      firstFreeLocal = local;
    locals[local] = 0;
  }

  /**
   * Save the current code offset. This saved code offset is used to compute
   * instruction counts in subsequent calls to {@link #addInstructionCount()}.
   */
  private void saveCurrentCodeOffset() {
    savedCodeOffset = cfw.getCurrentCodeOffset();
  }
  private void updateLineNumber(Node node) {
    itsLineNumber = node.getLineno();
    if (itsLineNumber == -1)
      return;
    cfw.addLineNumberEntry((short) itsLineNumber);
  }
  private boolean varIsDirectCallParameter(int varIndex) {
    return fnCurrent.isParameter(varIndex) && inDirectCallFunction
        && !itsForcedObjectParameters;
  }
  private void visitArithmetic(Node node, int opCode, Node child, Node parent) {
    int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
    if (childNumberFlag != -1) {
      generateExpression(child, node);
      generateExpression(child.getNext(), node);
      cfw.add(opCode);
    } else {
      boolean childOfArithmetic = isArithmeticNode(parent);
      generateExpression(child, node);
      if (!isArithmeticNode(child))
        addObjectToDouble();
      generateExpression(child.getNext(), node);
      if (!isArithmeticNode(child.getNext()))
        addObjectToDouble();
      cfw.add(opCode);
      if (!childOfArithmetic)
        addDoubleWrap();
    }
  }

  private void visitArrayLiteral(Node node, Node child) {
    int count = 0;
    for (Node cursor = child; cursor != null; cursor = cursor.getNext())
      ++count;
    // load array to store array literal objects
    addNewObjectArray(count);
    for (int i = 0; i != count; ++i) {
      cfw.add(ByteCode.DUP);
      cfw.addPush(i);
      generateExpression(child, node);
      cfw.add(ByteCode.AASTORE);
      child = child.getNext();
    }
    int[] skipIndexes = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
    if (skipIndexes == null) {
      cfw.add(ByteCode.ACONST_NULL);
      cfw.add(ByteCode.ICONST_0);
    } else {
      cfw.addPush(OptRuntime.encodeIntArray(skipIndexes));
      cfw.addPush(skipIndexes.length);
    }
    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    addOptRuntimeInvoke("newArrayLiteral", "([Ljava/lang/Object;"
        + "Ljava/lang/String;" + "I" + "Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + ")Lorg/mozilla/javascript/Scriptable;");
  }

  private void visitBitOp(Node node, int type, Node child) {
    int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
    generateExpression(child, node);

    // special-case URSH; work with the target arg as a long, so
    // that we can return a 32-bit unsigned value, and call
    // toUint32 instead of toInt32.
    if (type == Token.URSH) {
      addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J");
      generateExpression(child.getNext(), node);
      addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
      // Looks like we need to explicitly mask the shift to 5 bits -
      // LUSHR takes 6 bits.
      cfw.addPush(31);
      cfw.add(ByteCode.IAND);
      cfw.add(ByteCode.LUSHR);
      cfw.add(ByteCode.L2D);
      addDoubleWrap();
      return;
    }
    if (childNumberFlag == -1) {
      addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
      generateExpression(child.getNext(), node);
      addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
    } else {
      addScriptRuntimeInvoke("toInt32", "(D)I");
      generateExpression(child.getNext(), node);
      addScriptRuntimeInvoke("toInt32", "(D)I");
    }
    switch (type) {
      case Token.BITOR :
        cfw.add(ByteCode.IOR);
        break;
      case Token.BITXOR :
        cfw.add(ByteCode.IXOR);
        break;
      case Token.BITAND :
        cfw.add(ByteCode.IAND);
        break;
      case Token.RSH :
        cfw.add(ByteCode.ISHR);
        break;
      case Token.LSH :
        cfw.add(ByteCode.ISHL);
        break;
      default :
        throw Codegen.badTree();
    }
    cfw.add(ByteCode.I2D);
    if (childNumberFlag == -1)
      addDoubleWrap();
  }
  private void visitDotQuery(Node node, Node child) {
    updateLineNumber(node);
    generateExpression(child, node);
    cfw.addALoad(variableObjectLocal);
    addScriptRuntimeInvoke("enterDotQuery", "(Ljava/lang/Object;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + ")Lorg/mozilla/javascript/Scriptable;");
    cfw.addAStore(variableObjectLocal);

    // add push null/pop with label in between to simplify code for loop
    // continue when it is necessary to pop the null result from
    // updateDotQuery
    cfw.add(ByteCode.ACONST_NULL);
    int queryLoopStart = cfw.acquireLabel();
    cfw.markLabel(queryLoopStart); // loop continue jumps here
    cfw.add(ByteCode.POP);

    generateExpression(child.getNext(), node);
    addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
    cfw.addALoad(variableObjectLocal);
    addScriptRuntimeInvoke("updateDotQuery", "(Z"
        + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;");
    cfw.add(ByteCode.DUP);
    cfw.add(ByteCode.IFNULL, queryLoopStart);
    // stack: ... non_null_result_of_updateDotQuery
    cfw.addALoad(variableObjectLocal);
    addScriptRuntimeInvoke("leaveDotQuery",
        "(Lorg/mozilla/javascript/Scriptable;"
            + ")Lorg/mozilla/javascript/Scriptable;");
    cfw.addAStore(variableObjectLocal);
  }
  private void visitFunction(OptFunctionNode ofn, int functionType) {
    int fnIndex = codegen.getIndex(ofn.fnode);
    cfw.add(ByteCode.NEW, codegen.mainClassName);
    // Call function constructor
    cfw.add(ByteCode.DUP);
    cfw.addALoad(variableObjectLocal);
    cfw.addALoad(contextLocal); // load 'cx'
    cfw.addPush(fnIndex);
    cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, "<init>",
        Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);

    // Init mainScript field;
    cfw.add(ByteCode.DUP);
    if (isTopLevel)
      cfw.add(ByteCode.ALOAD_0);
    else {
      cfw.add(ByteCode.ALOAD_0);
      cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
          Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);
    }
    cfw.add(ByteCode.PUTFIELD, codegen.mainClassName,
        Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);

    int directTargetIndex = ofn.getDirectTargetIndex();
    if (directTargetIndex >= 0) {
      cfw.add(ByteCode.DUP);
      if (isTopLevel)
        cfw.add(ByteCode.ALOAD_0);
      else {
        cfw.add(ByteCode.ALOAD_0);
        cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
            Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);
      }
      cfw.add(ByteCode.SWAP);
      cfw.add(ByteCode.PUTFIELD, codegen.mainClassName, Codegen
          .getDirectTargetFieldName(directTargetIndex),
          codegen.mainClassSignature);
    }

    if (functionType == FunctionNode.FUNCTION_EXPRESSION)
      // Leave closure object on stack and do not pass it to
      // initFunction which suppose to connect statements to scope
      return;
    cfw.addPush(functionType);
    cfw.addALoad(variableObjectLocal);
    cfw.addALoad(contextLocal); // load 'cx'
    addOptRuntimeInvoke("initFunction",
        "(Lorg/mozilla/javascript/NativeFunction;" + "I"
            + "Lorg/mozilla/javascript/Scriptable;"
            + "Lorg/mozilla/javascript/Context;" + ")V");
  }
  private void visitGetProp(Node node, Node child) {
    generateExpression(child, node); // object
    Node nameChild = child.getNext();
    generateExpression(nameChild, node); // the name
    if (node.getType() == Token.GETPROPNOWARN) {
      cfw.addALoad(contextLocal);
      addScriptRuntimeInvoke("getObjectPropNoWarn", "(Ljava/lang/Object;"
          + "Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
          + ")Ljava/lang/Object;");
      return;
    }
    /*
     * for 'this.foo' we call getObjectProp(Scriptable...) which can skip some
     * casting overhead.
     */
    int childType = child.getType();
    if (childType == Token.THIS && nameChild.getType() == Token.STRING) {
      cfw.addALoad(contextLocal);
      addScriptRuntimeInvoke("getObjectProp",
          "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
              + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
    } else {
      cfw.addALoad(contextLocal);
      cfw.addALoad(variableObjectLocal);
      addScriptRuntimeInvoke("getObjectProp", "(Ljava/lang/Object;"
          + "Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
          + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;");
    }
  }
  private void visitGetVar(Node node) {
    if (!hasVarsInRegs)
      Kit.codeBug();
    int varIndex = fnCurrent.getVarIndex(node);
    short reg = varRegisters[varIndex];
    if (varIsDirectCallParameter(varIndex)) {
      // Remember that here the isNumber flag means that we
      // want to use the incoming parameter in a Number
      // context, so test the object type and convert the
      // value as necessary.
      if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
        dcpLoadAsNumber(reg);
      else
        dcpLoadAsObject(reg);
    } else if (fnCurrent.isNumberVar(varIndex))
      cfw.addDLoad(reg);
    else
      cfw.addALoad(reg);
  }
  private void visitGoto(Node.Jump node, int type, Node child) {
    Node target = node.target;
    if (type == Token.IFEQ || type == Token.IFNE) {
      if (child == null)
        throw Codegen.badTree();
      int targetLabel = getTargetLabel(target);
      int fallThruLabel = cfw.acquireLabel();
      if (type == Token.IFEQ)
        generateIfJump(child, node, targetLabel, fallThruLabel);
      else
        generateIfJump(child, node, fallThruLabel, targetLabel);
      cfw.markLabel(fallThruLabel);
    } else if (type == Token.JSR) {
      if (isGenerator)
        addGotoWithReturn(target);
      else
        addGoto(target, ByteCode.JSR);
    } else
      addGoto(target, ByteCode.GOTO);
  }

  private void visitIfJumpEqOp(Node node, Node child, int trueGOTO,
      int falseGOTO) {
    if (trueGOTO == -1 || falseGOTO == -1)
      throw Codegen.badTree();

    short stackInitial = cfw.getStackTop();
    int type = node.getType();
    Node rChild = child.getNext();

    // Optimize if one of operands is null
    if (child.getType() == Token.NULL || rChild.getType() == Token.NULL) {
      // eq is symmetric in this case
      if (child.getType() == Token.NULL)
        child = rChild;
      generateExpression(child, node);
      if (type == Token.SHEQ || type == Token.SHNE) {
        int testCode = type == Token.SHEQ
            ? ByteCode.IFNULL
            : ByteCode.IFNONNULL;
        cfw.add(testCode, trueGOTO);
      } else {
        if (type != Token.EQ) {
          // swap false/true targets for !=
          if (type != Token.NE)
            throw Codegen.badTree();
          int tmp = trueGOTO;
          trueGOTO = falseGOTO;
          falseGOTO = tmp;
        }
        cfw.add(ByteCode.DUP);
        int undefCheckLabel = cfw.acquireLabel();
        cfw.add(ByteCode.IFNONNULL, undefCheckLabel);
        short stack = cfw.getStackTop();
        cfw.add(ByteCode.POP);
        cfw.add(ByteCode.GOTO, trueGOTO);
        cfw.markLabel(undefCheckLabel, stack);
        Codegen.pushUndefined(cfw);
        cfw.add(ByteCode.IF_ACMPEQ, trueGOTO);
      }
      cfw.add(ByteCode.GOTO, falseGOTO);
    } else {
      int child_dcp_register = nodeIsDirectCallParameter(child);
      if (child_dcp_register != -1 && rChild.getType() == Token.TO_OBJECT) {
        Node convertChild = rChild.getFirstChild();
        if (convertChild.getType() == Token.NUMBER) {
          cfw.addALoad(child_dcp_register);
          cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
              "Ljava/lang/Class;");
          int notNumbersLabel = cfw.acquireLabel();
          cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel);
          cfw.addDLoad(child_dcp_register + 1);
          cfw.addPush(convertChild.getDouble());
          cfw.add(ByteCode.DCMPL);
          if (type == Token.EQ)
            cfw.add(ByteCode.IFEQ, trueGOTO);
          else
            cfw.add(ByteCode.IFNE, trueGOTO);
          cfw.add(ByteCode.GOTO, falseGOTO);
          cfw.markLabel(notNumbersLabel);
          // fall thru into generic handling
        }
      }

      generateExpression(child, node);
      generateExpression(rChild, node);

      String name;
      int testCode;
      switch (type) {
        case Token.EQ :
          name = "eq";
          testCode = ByteCode.IFNE;
          break;
        case Token.NE :
          name = "eq";
          testCode = ByteCode.IFEQ;
          break;
        case Token.SHEQ :
          name = "shallowEq";
          testCode = ByteCode.IFNE;
          break;
        case Token.SHNE :
          name = "shallowEq";
          testCode = ByteCode.IFEQ;
          break;
        default :
          throw Codegen.badTree();
      }
      addScriptRuntimeInvoke(name, "(Ljava/lang/Object;" + "Ljava/lang/Object;"
          + ")Z");
      cfw.add(testCode, trueGOTO);
      cfw.add(ByteCode.GOTO, falseGOTO);
    }
    if (stackInitial != cfw.getStackTop())
      throw Codegen.badTree();
  }
  private void visitIfJumpRelOp(Node node, Node child, int trueGOTO,
      int falseGOTO) {
    if (trueGOTO == -1 || falseGOTO == -1)
      throw Codegen.badTree();
    int type = node.getType();
    Node rChild = child.getNext();
    if (type == Token.INSTANCEOF || type == Token.IN) {
      generateExpression(child, node);
      generateExpression(rChild, node);
      cfw.addALoad(contextLocal);
      addScriptRuntimeInvoke(type == Token.INSTANCEOF ? "instanceOf" : "in",
          "(Ljava/lang/Object;" + "Ljava/lang/Object;"
              + "Lorg/mozilla/javascript/Context;" + ")Z");
      cfw.add(ByteCode.IFNE, trueGOTO);
      cfw.add(ByteCode.GOTO, falseGOTO);
      return;
    }
    int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
    int left_dcp_register = nodeIsDirectCallParameter(child);
    int right_dcp_register = nodeIsDirectCallParameter(rChild);
    if (childNumberFlag != -1) {
      // Force numeric context on both parameters and optimize
      // direct call case as Optimizer currently does not handle it

      if (childNumberFlag != Node.RIGHT)
        // Left already has number content
        generateExpression(child, node);
      else if (left_dcp_register != -1)
        dcpLoadAsNumber(left_dcp_register);
      else {
        generateExpression(child, node);
        addObjectToDouble();
      }

      if (childNumberFlag != Node.LEFT)
        // Right already has number content
        generateExpression(rChild, node);
      else if (right_dcp_register != -1)
        dcpLoadAsNumber(right_dcp_register);
      else {
        generateExpression(rChild, node);
        addObjectToDouble();
      }

      genSimpleCompare(type, trueGOTO, falseGOTO);

    } else {
      if (left_dcp_register != -1 && right_dcp_register != -1) {
        // Generate code to dynamically check for number content
        // if both operands are dcp
        short stack = cfw.getStackTop();
        int leftIsNotNumber = cfw.acquireLabel();
        cfw.addALoad(left_dcp_register);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
            "Ljava/lang/Class;");
        cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber);
        cfw.addDLoad(left_dcp_register + 1);
        dcpLoadAsNumber(right_dcp_register);
        genSimpleCompare(type, trueGOTO, falseGOTO);
        if (stack != cfw.getStackTop())
          throw Codegen.badTree();

        cfw.markLabel(leftIsNotNumber);
        int rightIsNotNumber = cfw.acquireLabel();
        cfw.addALoad(right_dcp_register);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
            "Ljava/lang/Class;");
        cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber);
        cfw.addALoad(left_dcp_register);
        addObjectToDouble();
        cfw.addDLoad(right_dcp_register + 1);
        genSimpleCompare(type, trueGOTO, falseGOTO);
        if (stack != cfw.getStackTop())
          throw Codegen.badTree();

        cfw.markLabel(rightIsNotNumber);
        // Load both register as objects to call generic cmp_*
        cfw.addALoad(left_dcp_register);
        cfw.addALoad(right_dcp_register);

      } else {
        generateExpression(child, node);
        generateExpression(rChild, node);
      }

      if (type == Token.GE || type == Token.GT)
        cfw.add(ByteCode.SWAP);
      String routine = type == Token.LT || type == Token.GT
          ? "cmp_LT"
          : "cmp_LE";
      addScriptRuntimeInvoke(routine, "(Ljava/lang/Object;"
          + "Ljava/lang/Object;" + ")Z");
      cfw.add(ByteCode.IFNE, trueGOTO);
      cfw.add(ByteCode.GOTO, falseGOTO);
    }
  }
  private void visitIncDec(Node node) {
    int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
    Node child = node.getFirstChild();
    switch (child.getType()) {
      case Token.GETVAR :
        if (!hasVarsInRegs)
          Kit.codeBug();
        if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
          boolean post = (incrDecrMask & Node.POST_FLAG) != 0;
          int varIndex = fnCurrent.getVarIndex(child);
          short reg = varRegisters[varIndex];
          int offset = varIsDirectCallParameter(varIndex) ? 1 : 0;
          cfw.addDLoad(reg + offset);
          if (post)
            cfw.add(ByteCode.DUP2);
          cfw.addPush(1.0);
          if ((incrDecrMask & Node.DECR_FLAG) == 0)
            cfw.add(ByteCode.DADD);
          else
            cfw.add(ByteCode.DSUB);
          if (!post)
            cfw.add(ByteCode.DUP2);
          cfw.addDStore(reg + offset);
        } else {
          boolean post = (incrDecrMask & Node.POST_FLAG) != 0;
          int varIndex = fnCurrent.getVarIndex(child);
          short reg = varRegisters[varIndex];
          cfw.addALoad(reg);
          if (post)
            cfw.add(ByteCode.DUP);
          addObjectToDouble();
          cfw.addPush(1.0);
          if ((incrDecrMask & Node.DECR_FLAG) == 0)
            cfw.add(ByteCode.DADD);
          else
            cfw.add(ByteCode.DSUB);
          addDoubleWrap();
          if (!post)
            cfw.add(ByteCode.DUP);
          cfw.addAStore(reg);
          break;
        }
        break;
      case Token.NAME :
        cfw.addALoad(variableObjectLocal);
        cfw.addPush(child.getString()); // push name
        cfw.addALoad(contextLocal);
        cfw.addPush(incrDecrMask);
        addScriptRuntimeInvoke("nameIncrDecr",
            "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
                + "Lorg/mozilla/javascript/Context;" + "I)Ljava/lang/Object;");
        break;
      case Token.GETPROPNOWARN :
        throw Kit.codeBug();
      case Token.GETPROP : {
        Node getPropChild = child.getFirstChild();
        generateExpression(getPropChild, node);
        generateExpression(getPropChild.getNext(), node);
        cfw.addALoad(contextLocal);
        cfw.addPush(incrDecrMask);
        addScriptRuntimeInvoke("propIncrDecr", "(Ljava/lang/Object;"
            + "Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
            + "I)Ljava/lang/Object;");
        break;
      }
      case Token.GETELEM : {
        Node elemChild = child.getFirstChild();
        generateExpression(elemChild, node);
        generateExpression(elemChild.getNext(), node);
        cfw.addALoad(contextLocal);
        cfw.addPush(incrDecrMask);
        if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1)
          addOptRuntimeInvoke("elemIncrDecr", "(Ljava/lang/Object;" + "D"
              + "Lorg/mozilla/javascript/Context;" + "I"
              + ")Ljava/lang/Object;");
        else
          addScriptRuntimeInvoke("elemIncrDecr", "(Ljava/lang/Object;"
              + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;" + "I"
              + ")Ljava/lang/Object;");
        break;
      }
      case Token.GET_REF : {
        Node refChild = child.getFirstChild();
        generateExpression(refChild, node);
        cfw.addALoad(contextLocal);
        cfw.addPush(incrDecrMask);
        addScriptRuntimeInvoke("refIncrDecr", "(Lorg/mozilla/javascript/Ref;"
            + "Lorg/mozilla/javascript/Context;" + "I)Ljava/lang/Object;");
        break;
      }
      default :
        Codegen.badTree();
    }
  }
  private void visitObjectLiteral(Node node, Node child) {
    Object[] properties = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);
    int count = properties.length;

    // load array with property ids
    addNewObjectArray(count);
    for (int i = 0; i != count; ++i) {
      cfw.add(ByteCode.DUP);
      cfw.addPush(i);
      Object id = properties[i];
      if (id instanceof String)
        cfw.addPush((String) id);
      else {
        cfw.addPush(((Integer) id).intValue());
        addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;");
      }
      cfw.add(ByteCode.AASTORE);
    }
    // load array with property values
    addNewObjectArray(count);
    Node child2 = child;
    for (int i = 0; i != count; ++i) {
      cfw.add(ByteCode.DUP);
      cfw.addPush(i);
      int childType = child.getType();
      if (childType == Token.GET)
        generateExpression(child.getFirstChild(), node);
      else if (childType == Token.SET)
        generateExpression(child.getFirstChild(), node);
      else
        generateExpression(child, node);
      cfw.add(ByteCode.AASTORE);
      child = child.getNext();
    }
    // load array with getterSetter values
    cfw.addPush(count);
    cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT);
    for (int i = 0; i != count; ++i) {
      cfw.add(ByteCode.DUP);
      cfw.addPush(i);
      int childType = child2.getType();
      if (childType == Token.GET)
        cfw.add(ByteCode.ICONST_M1);
      else if (childType == Token.SET)
        cfw.add(ByteCode.ICONST_1);
      else
        cfw.add(ByteCode.ICONST_0);
      cfw.add(ByteCode.IASTORE);
      child2 = child2.getNext();
    }

    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    addScriptRuntimeInvoke("newObjectLiteral", "([Ljava/lang/Object;"
        + "[Ljava/lang/Object;" + "[I" + "Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + ")Lorg/mozilla/javascript/Scriptable;");
  }
  private void visitOptimizedCall(Node node, OptFunctionNode target, int type,
      Node child) {
    Node firstArgChild = child.getNext();

    short thisObjLocal = 0;
    if (type == Token.NEW)
      generateExpression(child, node);
    else {
      generateFunctionAndThisObj(child, node);
      thisObjLocal = getNewWordLocal();
      cfw.addAStore(thisObjLocal);
    }
    // stack: ... functionObj

    int beyond = cfw.acquireLabel();

    int directTargetIndex = target.getDirectTargetIndex();
    if (isTopLevel)
      cfw.add(ByteCode.ALOAD_0);
    else {
      cfw.add(ByteCode.ALOAD_0);
      cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
          Codegen.DIRECT_CALL_PARENT_FIELD, codegen.mainClassSignature);
    }
    cfw.add(ByteCode.GETFIELD, codegen.mainClassName, Codegen
        .getDirectTargetFieldName(directTargetIndex),
        codegen.mainClassSignature);

    cfw.add(ByteCode.DUP2);
    // stack: ... functionObj directFunct functionObj directFunct

    int regularCall = cfw.acquireLabel();
    cfw.add(ByteCode.IF_ACMPNE, regularCall);

    // stack: ... functionObj directFunct
    short stackHeight = cfw.getStackTop();
    cfw.add(ByteCode.SWAP);
    cfw.add(ByteCode.POP);
    // stack: ... directFunct
    if (compilerEnv.isUseDynamicScope()) {
      cfw.addALoad(contextLocal);
      cfw.addALoad(variableObjectLocal);
    } else {
      cfw.add(ByteCode.DUP);
      // stack: ... directFunct directFunct
      cfw.addInvoke(ByteCode.INVOKEINTERFACE,
          "org/mozilla/javascript/Scriptable", "getParentScope",
          "()Lorg/mozilla/javascript/Scriptable;");
      // stack: ... directFunct scope
      cfw.addALoad(contextLocal);
      // stack: ... directFunct scope cx
      cfw.add(ByteCode.SWAP);
    }
    // stack: ... directFunc cx scope

    if (type == Token.NEW)
      cfw.add(ByteCode.ACONST_NULL);
    else
      cfw.addALoad(thisObjLocal);
    // stack: ... directFunc cx scope thisObj
    /*
     * Remember that directCall parameters are paired in 1 aReg and 1 dReg If
     * the argument is an incoming arg, just pass the orginal pair thru. Else,
     * if the argument is known to be typed 'Number', pass Void.TYPE in the aReg
     * and the number is the dReg Else pass the JS object in the aReg and 0.0 in
     * the dReg.
     */
    Node argChild = firstArgChild;
    while (argChild != null) {
      int dcp_register = nodeIsDirectCallParameter(argChild);
      if (dcp_register >= 0) {
        cfw.addALoad(dcp_register);
        cfw.addDLoad(dcp_register + 1);
      } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1) == Node.BOTH) {
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
            "Ljava/lang/Class;");
        generateExpression(argChild, node);
      } else {
        generateExpression(argChild, node);
        cfw.addPush(0.0);
      }
      argChild = argChild.getNext();
    }

    cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/ScriptRuntime",
        "emptyArgs", "[Ljava/lang/Object;");
    cfw.addInvoke(ByteCode.INVOKESTATIC, codegen.mainClassName,
        type == Token.NEW ? codegen.getDirectCtorName(target.fnode) : codegen
            .getBodyMethodName(target.fnode), codegen
            .getBodyMethodSignature(target.fnode));

    cfw.add(ByteCode.GOTO, beyond);

    cfw.markLabel(regularCall, stackHeight);
    // stack: ... functionObj directFunct
    cfw.add(ByteCode.POP);
    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    // stack: ... functionObj cx scope
    if (type != Token.NEW) {
      cfw.addALoad(thisObjLocal);
      releaseWordLocal(thisObjLocal);
      // stack: ... functionObj cx scope thisObj
    }
    // XXX: this will generate code for the child array the second time,
    // so expression code generation better not to alter tree structure...
    generateCallArgArray(node, firstArgChild, true);

    if (type == Token.NEW)
      addScriptRuntimeInvoke("newObject", "(Ljava/lang/Object;"
          + "Lorg/mozilla/javascript/Context;"
          + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
          + ")Lorg/mozilla/javascript/Scriptable;");
    else
      cfw.addInvoke(ByteCode.INVOKEINTERFACE,
          "org/mozilla/javascript/Callable", "call",
          "(Lorg/mozilla/javascript/Context;"
              + "Lorg/mozilla/javascript/Scriptable;"
              + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
              + ")Ljava/lang/Object;");

    cfw.markLabel(beyond);
  }
  private void visitSetConst(Node node, Node child) {
    String name = node.getFirstChild().getString();
    while (child != null) {
      generateExpression(child, node);
      child = child.getNext();
    }
    cfw.addALoad(contextLocal);
    cfw.addPush(name);
    addScriptRuntimeInvoke("setConst", "(Lorg/mozilla/javascript/Scriptable;"
        + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
        + "Ljava/lang/String;" + ")Ljava/lang/Object;");
  }
  private void visitSetConstVar(Node node, Node child, boolean needValue) {
    if (!hasVarsInRegs)
      Kit.codeBug();
    int varIndex = fnCurrent.getVarIndex(node);
    generateExpression(child.getNext(), node);
    boolean isNumber = node.getIntProp(Node.ISNUMBER_PROP, -1) != -1;
    short reg = varRegisters[varIndex];
    int beyond = cfw.acquireLabel();
    int noAssign = cfw.acquireLabel();
    if (isNumber) {
      cfw.addILoad(reg + 2);
      cfw.add(ByteCode.IFNE, noAssign);
      short stack = cfw.getStackTop();
      cfw.addPush(1);
      cfw.addIStore(reg + 2);
      cfw.addDStore(reg);
      if (needValue) {
        cfw.addDLoad(reg);
        cfw.markLabel(noAssign, stack);
      } else {
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(noAssign, stack);
        cfw.add(ByteCode.POP2);
      }
    } else {
      cfw.addILoad(reg + 1);
      cfw.add(ByteCode.IFNE, noAssign);
      short stack = cfw.getStackTop();
      cfw.addPush(1);
      cfw.addIStore(reg + 1);
      cfw.addAStore(reg);
      if (needValue) {
        cfw.addALoad(reg);
        cfw.markLabel(noAssign, stack);
      } else {
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(noAssign, stack);
        cfw.add(ByteCode.POP);
      }
    }
    cfw.markLabel(beyond);
  }
  private void visitSetElem(int type, Node node, Node child) {
    generateExpression(child, node);
    child = child.getNext();
    if (type == Token.SETELEM_OP)
      cfw.add(ByteCode.DUP);
    generateExpression(child, node);
    child = child.getNext();
    boolean indexIsNumber = node.getIntProp(Node.ISNUMBER_PROP, -1) != -1;
    if (type == Token.SETELEM_OP)
      if (indexIsNumber) {
        // stack: ... object object number
        // -> ... object number object number
        cfw.add(ByteCode.DUP2_X1);
        cfw.addALoad(contextLocal);
        addOptRuntimeInvoke("getObjectIndex", "(Ljava/lang/Object;D"
            + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
      } else {
        // stack: ... object object indexObject
        // -> ... object indexObject object indexObject
        cfw.add(ByteCode.DUP_X1);
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("getObjectElem", "(Ljava/lang/Object;"
            + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
            + ")Ljava/lang/Object;");
      }
    generateExpression(child, node);
    cfw.addALoad(contextLocal);
    if (indexIsNumber)
      addScriptRuntimeInvoke("setObjectIndex", "(Ljava/lang/Object;" + "D"
          + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
          + ")Ljava/lang/Object;");
    else
      addScriptRuntimeInvoke("setObjectElem", "(Ljava/lang/Object;"
          + "Ljava/lang/Object;" + "Ljava/lang/Object;"
          + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
  }
  private void visitSetName(Node node, Node child) {
    String name = node.getFirstChild().getString();
    while (child != null) {
      generateExpression(child, node);
      child = child.getNext();
    }
    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    cfw.addPush(name);
    addScriptRuntimeInvoke("setName", "(Lorg/mozilla/javascript/Scriptable;"
        + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
        + ")Ljava/lang/Object;");
  }
  private void visitSetProp(int type, Node node, Node child) {
    Node objectChild = child;
    generateExpression(child, node);
    child = child.getNext();
    if (type == Token.SETPROP_OP)
      cfw.add(ByteCode.DUP);
    Node nameChild = child;
    generateExpression(child, node);
    child = child.getNext();
    if (type == Token.SETPROP_OP) {
      // stack: ... object object name -> ... object name object name
      cfw.add(ByteCode.DUP_X1);
      // for 'this.foo += ...' we call thisGet which can skip some
      // casting overhead.
      if (objectChild.getType() == Token.THIS
          && nameChild.getType() == Token.STRING) {
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("getObjectProp",
            "(Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/String;"
                + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
      } else {
        cfw.addALoad(contextLocal);
        addScriptRuntimeInvoke("getObjectProp", "(Ljava/lang/Object;"
            + "Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
            + ")Ljava/lang/Object;");
      }
    }
    generateExpression(child, node);
    cfw.addALoad(contextLocal);
    addScriptRuntimeInvoke("setObjectProp", "(Ljava/lang/Object;"
        + "Ljava/lang/String;" + "Ljava/lang/Object;"
        + "Lorg/mozilla/javascript/Context;" + ")Ljava/lang/Object;");
  }
  private void visitSetVar(Node node, Node child, boolean needValue) {
    if (!hasVarsInRegs)
      Kit.codeBug();
    int varIndex = fnCurrent.getVarIndex(node);
    generateExpression(child.getNext(), node);
    boolean isNumber = node.getIntProp(Node.ISNUMBER_PROP, -1) != -1;
    short reg = varRegisters[varIndex];
    boolean[] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
    if (constDeclarations[varIndex]) {
      if (!needValue)
        if (isNumber)
          cfw.add(ByteCode.POP2);
        else
          cfw.add(ByteCode.POP);
    } else if (varIsDirectCallParameter(varIndex)) {
      if (isNumber) {
        if (needValue)
          cfw.add(ByteCode.DUP2);
        cfw.addALoad(reg);
        cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
            "Ljava/lang/Class;");
        int isNumberLabel = cfw.acquireLabel();
        int beyond = cfw.acquireLabel();
        cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
        short stack = cfw.getStackTop();
        addDoubleWrap();
        cfw.addAStore(reg);
        cfw.add(ByteCode.GOTO, beyond);
        cfw.markLabel(isNumberLabel, stack);
        cfw.addDStore(reg + 1);
        cfw.markLabel(beyond);
      } else {
        if (needValue)
          cfw.add(ByteCode.DUP);
        cfw.addAStore(reg);
      }
    } else {
      boolean isNumberVar = fnCurrent.isNumberVar(varIndex);
      if (isNumber) {
        if (isNumberVar) {
          cfw.addDStore(reg);
          if (needValue)
            cfw.addDLoad(reg);
        } else {
          if (needValue)
            cfw.add(ByteCode.DUP2);
          // Cannot save number in variable since !isNumberVar,
          // so convert to object
          addDoubleWrap();
          cfw.addAStore(reg);
        }
      } else {
        if (isNumberVar)
          Kit.codeBug();
        cfw.addAStore(reg);
        if (needValue)
          cfw.addALoad(reg);
      }
    }
  }

  private void visitSpecialCall(Node node, int type, int specialType, Node child) {
    cfw.addALoad(contextLocal);

    if (type == Token.NEW)
      generateExpression(child, node);
    // stack: ... cx functionObj
    else
      generateFunctionAndThisObj(child, node);
    // stack: ... cx functionObj thisObj
    child = child.getNext();

    generateCallArgArray(node, child, false);

    String methodName;
    String callSignature;

    if (type == Token.NEW) {
      methodName = "newObjectSpecial";
      callSignature = "(Lorg/mozilla/javascript/Context;"
          + "Ljava/lang/Object;" + "[Ljava/lang/Object;"
          + "Lorg/mozilla/javascript/Scriptable;"
          + "Lorg/mozilla/javascript/Scriptable;" + "I" // call type
          + ")Ljava/lang/Object;";
      cfw.addALoad(variableObjectLocal);
      cfw.addALoad(thisObjLocal);
      cfw.addPush(specialType);
    } else {
      methodName = "callSpecial";
      callSignature = "(Lorg/mozilla/javascript/Context;"
          + "Lorg/mozilla/javascript/Callable;"
          + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
          + "Lorg/mozilla/javascript/Scriptable;"
          + "Lorg/mozilla/javascript/Scriptable;" + "I" // call type
          + "Ljava/lang/String;I" // filename, linenumber
          + ")Ljava/lang/Object;";
      cfw.addALoad(variableObjectLocal);
      cfw.addALoad(thisObjLocal);
      cfw.addPush(specialType);
      String sourceName = scriptOrFn.getSourceName();
      cfw.addPush(sourceName == null ? "" : sourceName);
      cfw.addPush(itsLineNumber);
    }

    addOptRuntimeInvoke(methodName, callSignature);
  }
  private void visitStandardCall(Node node, Node child) {
    if (node.getType() != Token.CALL)
      throw Codegen.badTree();

    Node firstArgChild = child.getNext();
    int childType = child.getType();

    String methodName;
    String signature;

    if (firstArgChild == null) {
      if (childType == Token.NAME) {
        // name() call
        String name = child.getString();
        cfw.addPush(name);
        methodName = "callName0";
        signature = "(Ljava/lang/String;" + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      } else if (childType == Token.GETPROP) {
        // x.name() call
        Node propTarget = child.getFirstChild();
        generateExpression(propTarget, node);
        Node id = propTarget.getNext();
        String property = id.getString();
        cfw.addPush(property);
        methodName = "callProp0";
        signature = "(Ljava/lang/Object;" + "Ljava/lang/String;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      } else if (childType == Token.GETPROPNOWARN)
        throw Kit.codeBug();
      else {
        generateFunctionAndThisObj(child, node);
        methodName = "call0";
        signature = "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      }

    } else if (childType == Token.NAME) {
      // XXX: this optimization is only possible if name
      // resolution
      // is not affected by arguments evaluation and currently
      // there are no checks for it
      String name = child.getString();
      generateCallArgArray(node, firstArgChild, false);
      cfw.addPush(name);
      methodName = "callName";
      signature = "([Ljava/lang/Object;" + "Ljava/lang/String;"
          + "Lorg/mozilla/javascript/Context;"
          + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
    } else {
      int argCount = 0;
      for (Node arg = firstArgChild; arg != null; arg = arg.getNext())
        ++argCount;
      generateFunctionAndThisObj(child, node);
      // stack: ... functionObj thisObj
      if (argCount == 1) {
        generateExpression(firstArgChild, node);
        methodName = "call1";
        signature = "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      } else if (argCount == 2) {
        generateExpression(firstArgChild, node);
        generateExpression(firstArgChild.getNext(), node);
        methodName = "call2";
        signature = "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;"
            + "Ljava/lang/Object;" + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      } else {
        generateCallArgArray(node, firstArgChild, false);
        methodName = "callN";
        signature = "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;";
      }
    }

    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    addOptRuntimeInvoke(methodName, signature);
  }
  private void visitStandardNew(Node node, Node child) {
    if (node.getType() != Token.NEW)
      throw Codegen.badTree();

    Node firstArgChild = child.getNext();

    generateExpression(child, node);
    // stack: ... functionObj
    cfw.addALoad(contextLocal);
    cfw.addALoad(variableObjectLocal);
    // stack: ... functionObj cx scope
    generateCallArgArray(node, firstArgChild, false);
    addScriptRuntimeInvoke("newObject", "(Ljava/lang/Object;"
        + "Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
        + ")Lorg/mozilla/javascript/Scriptable;");
  }
  private void visitSwitch(Node.Jump switchNode, Node child) {
    // See comments in IRFactory.createSwitch() for description
    // of SWITCH node

    generateExpression(child, switchNode);
    // save selector value
    short selector = getNewWordLocal();
    cfw.addAStore(selector);

    for (Node.Jump caseNode = (Node.Jump) child.getNext(); caseNode != null; caseNode = (Node.Jump) caseNode
        .getNext()) {
      if (caseNode.getType() != Token.CASE)
        throw Codegen.badTree();
      Node test = caseNode.getFirstChild();
      generateExpression(test, caseNode);
      cfw.addALoad(selector);
      addScriptRuntimeInvoke("shallowEq", "(Ljava/lang/Object;"
          + "Ljava/lang/Object;" + ")Z");
      addGoto(caseNode.target, ByteCode.IFNE);
    }
    releaseWordLocal(selector);
  }

  private void visitTryCatchFinally(Node.Jump node, Node child) {
    /*
     * Save the variable object, in case there are with statements enclosed by
     * the try block and we catch some exception. We'll restore it for the catch
     * block so that catch block statements get the right scope.
     */

    // OPT we only need to do this if there are enclosed WITH
    // statements; could statically check and omit this if there aren't any.

    // XXX OPT Maybe instead do syntactic transforms to associate
    // each 'with' with a try/finally block that does the exitwith.

    short savedVariableObject = getNewWordLocal();
    cfw.addALoad(variableObjectLocal);
    cfw.addAStore(savedVariableObject);

    /*
     * Generate the code for the tree; most of the work is done in IRFactory and
     * NodeTransformer; Codegen just adds the java handlers for the javascript
     * catch and finally clauses.
     */

    int startLabel = cfw.acquireLabel();
    cfw.markLabel(startLabel, (short) 0);

    Node catchTarget = node.target;
    Node finallyTarget = node.getFinally();

    // create a table for the equivalent of JSR returns
    if (isGenerator && finallyTarget != null) {
      FinallyReturnPoint ret = new FinallyReturnPoint();
      if (finallys == null)
        finallys = new HashMap<Node, FinallyReturnPoint>();
      // add the finally target to hashtable
      finallys.put(finallyTarget, ret);
      // add the finally node as well to the hash table
      finallys.put(finallyTarget.getNext(), ret);
    }

    while (child != null) {
      generateStatement(child);
      child = child.getNext();
    }

    // control flow skips the handlers
    int realEnd = cfw.acquireLabel();
    cfw.add(ByteCode.GOTO, realEnd);

    int exceptionLocal = getLocalBlockRegister(node);
    // javascript handler; unwrap exception and GOTO to javascript
    // catch area.
    if (catchTarget != null) {
      // get the label to goto
      int catchLabel = catchTarget.labelId();

      generateCatchBlock(JAVASCRIPT_EXCEPTION, savedVariableObject, catchLabel,
          startLabel, exceptionLocal);
      /*
       * catch WrappedExceptions, see if they are wrapped JavaScriptExceptions.
       * Otherwise, rethrow.
       */
      generateCatchBlock(EVALUATOR_EXCEPTION, savedVariableObject, catchLabel,
          startLabel, exceptionLocal);

      /*
       * we also need to catch EcmaErrors and feed the associated error object
       * to the handler
       */
      generateCatchBlock(ECMAERROR_EXCEPTION, savedVariableObject, catchLabel,
          startLabel, exceptionLocal);

      Context cx = Context.getCurrentContext();
      if (cx != null && cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS))
        generateCatchBlock(THROWABLE_EXCEPTION, savedVariableObject,
            catchLabel, startLabel, exceptionLocal);
    }

    // finally handler; catch all exceptions, store to a local; JSR to
    // the finally, then re-throw.
    if (finallyTarget != null) {
      int finallyHandler = cfw.acquireLabel();
      cfw.markHandler(finallyHandler);
      cfw.addAStore(exceptionLocal);

      // reset the variable object local
      cfw.addALoad(savedVariableObject);
      cfw.addAStore(variableObjectLocal);

      // get the label to JSR to
      int finallyLabel = finallyTarget.labelId();
      if (isGenerator)
        addGotoWithReturn(finallyTarget);
      else
        cfw.add(ByteCode.JSR, finallyLabel);

      // rethrow
      cfw.addALoad(exceptionLocal);
      if (isGenerator)
        cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
      cfw.add(ByteCode.ATHROW);

      // mark the handler
      cfw.addExceptionHandler(startLabel, finallyLabel, finallyHandler, null); // catch
                                                                               // any
    }
    releaseWordLocal(savedVariableObject);
    cfw.markLabel(realEnd);
  }

  private void visitTypeofname(Node node) {
    if (hasVarsInRegs) {
      int varIndex = fnCurrent.fnode.getIndexForNameNode(node);
      if (varIndex >= 0) {
        if (fnCurrent.isNumberVar(varIndex))
          cfw.addPush("number");
        else if (varIsDirectCallParameter(varIndex)) {
          int dcp_register = varRegisters[varIndex];
          cfw.addALoad(dcp_register);
          cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
              "Ljava/lang/Class;");
          int isNumberLabel = cfw.acquireLabel();
          cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
          short stack = cfw.getStackTop();
          cfw.addALoad(dcp_register);
          addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;"
              + ")Ljava/lang/String;");
          int beyond = cfw.acquireLabel();
          cfw.add(ByteCode.GOTO, beyond);
          cfw.markLabel(isNumberLabel, stack);
          cfw.addPush("number");
          cfw.markLabel(beyond);
        } else {
          cfw.addALoad(varRegisters[varIndex]);
          addScriptRuntimeInvoke("typeof", "(Ljava/lang/Object;"
              + ")Ljava/lang/String;");
        }
        return;
      }
    }
    cfw.addALoad(variableObjectLocal);
    cfw.addPush(node.getString());
    addScriptRuntimeInvoke("typeofName", "(Lorg/mozilla/javascript/Scriptable;"
        + "Ljava/lang/String;" + ")Ljava/lang/String;");
  }
}

/**
 * This class generates code for a given IR tree.
 * 
 * @author Norris Boyd
 * @author Roger Lawrence
 */

public class Codegen implements Evaluator {
  static final String DEFAULT_MAIN_METHOD_CLASS = "org.mozilla.javascript.optimizer.OptRuntime";

  private static final String SUPER_CLASS_NAME = "org.mozilla.javascript.NativeFunction";

  static final String DIRECT_CALL_PARENT_FIELD = "_dcp";

  private static final String ID_FIELD_NAME = "_id";

  private static final String REGEXP_INIT_METHOD_NAME = "_reInit";

  private static final String REGEXP_INIT_METHOD_SIGNATURE = "(Lorg/mozilla/javascript/RegExpProxy;"
      + "Lorg/mozilla/javascript/Context;" + ")V";

  static final String REGEXP_ARRAY_FIELD_NAME = "_re";

  static final String REGEXP_ARRAY_FIELD_TYPE = "[Ljava/lang/Object;";

  static final String FUNCTION_INIT_SIGNATURE = "(Lorg/mozilla/javascript/Context;"
      + "Lorg/mozilla/javascript/Scriptable;" + ")V";

  static final String FUNCTION_CONSTRUCTOR_SIGNATURE = "(Lorg/mozilla/javascript/Scriptable;"
      + "Lorg/mozilla/javascript/Context;I)V";

  private static final Object globalLock = new Object();

  private static int globalSerialClassCounter;

  private static void addDoubleWrap(ClassFileWriter cfw) {
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/optimizer/OptRuntime", "wrapDouble",
        "(D)Ljava/lang/Double;");
  }

  static RuntimeException badTree() {
    throw new RuntimeException("Bad tree in codegen");
  }

  private static void collectScriptOrFnNodes_r(ScriptOrFnNode n, ObjArray x) {
    x.add(n);
    int nestedCount = n.getFunctionCount();
    for (int i = 0; i != nestedCount; ++i)
      collectScriptOrFnNodes_r(n.getFunctionNode(i), x);
  }

  static String getDirectTargetFieldName(int i) {
    return "_dt" + i;
  }

  private static String getStaticConstantWrapperType(double num) {
    int inum = (int) num;
    if (inum == num)
      return "Ljava/lang/Integer;";
    else
      return "Ljava/lang/Double;";
  }

  private static void initOptFunctions_r(ScriptOrFnNode scriptOrFn) {
    for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) {
      FunctionNode fn = scriptOrFn.getFunctionNode(i);
      new OptFunctionNode(fn);
      initOptFunctions_r(fn);
    }
  }

  static boolean isGenerator(ScriptOrFnNode node) {
    return node.getType() == Token.FUNCTION
        && ((FunctionNode) node).isGenerator();
  }

  static void pushUndefined(ClassFileWriter cfw) {
    cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined", "instance",
        "Ljava/lang/Object;");
  }

  private CompilerEnvirons compilerEnv;

  private ObjArray directCallTargets;

  ScriptOrFnNode[] scriptOrFnNodes;

  private ObjToIntMap scriptOrFnIndexes;

  private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;

  String mainClassName;

  String mainClassSignature;

  private double[] itsConstantList;

  private int itsConstantListSize;

  public void captureStackInfo(RhinoException ex) {
    throw new UnsupportedOperationException();
  }

  public Object compile(CompilerEnvirons compilerEnv, ScriptOrFnNode tree,
      String encodedSource, boolean returnFunction) {
    int serial;
    synchronized (globalLock) {
      serial = ++globalSerialClassCounter;
    }
    String mainClassName = "org.mozilla.javascript.gen.c" + serial;

    byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName,
        tree, encodedSource, returnFunction);

    return new Object[]{mainClassName, mainClassBytes};
  }

  byte[] compileToClassFile(CompilerEnvirons compilerEnv, String mainClassName,
      ScriptOrFnNode scriptOrFn, String encodedSource, boolean returnFunction) {
    this.compilerEnv = compilerEnv;

    transform(scriptOrFn);

    if (Token.printTrees)
      System.out.println(scriptOrFn.toStringTree(scriptOrFn));

    if (returnFunction)
      scriptOrFn = scriptOrFn.getFunctionNode(0);

    initScriptOrFnNodesData(scriptOrFn);

    this.mainClassName = mainClassName;
    mainClassSignature = ClassFileWriter.classNameToSignature(mainClassName);

    try {
      return generateCode(encodedSource);
    } catch (ClassFileWriter.ClassFileFormatException e) {
      throw reportClassFileFormatException(scriptOrFn, e.getMessage());
    }
  }
  public Function createFunctionObject(Context cx, Scriptable scope,
      Object bytecode, Object staticSecurityDomain) {
    Class<?> cl = defineClass(bytecode, staticSecurityDomain);

    NativeFunction f;
    try {
      Constructor<?> ctor = cl.getConstructors()[0];
      Object[] initArgs = {scope, cx, new Integer(0)};
      f = (NativeFunction) ctor.newInstance(initArgs);
    } catch (Exception ex) {
      throw new RuntimeException("Unable to instantiate compiled class:"
          + ex.toString());
    }
    return f;
  }

  public Script createScriptObject(Object bytecode, Object staticSecurityDomain) {
    Class<?> cl = defineClass(bytecode, staticSecurityDomain);

    Script script;
    try {
      script = (Script) cl.newInstance();
    } catch (Exception ex) {
      throw new RuntimeException("Unable to instantiate compiled class:"
          + ex.toString());
    }
    return script;
  }

  private Class<?> defineClass(Object bytecode, Object staticSecurityDomain) {
    Object[] nameBytesPair = (Object[]) bytecode;
    String className = (String) nameBytesPair[0];
    byte[] classBytes = (byte[]) nameBytesPair[1];

    // The generated classes in this case refer only to Rhino classes
    // which must be accessible through this class loader
    ClassLoader rhinoLoader = getClass().getClassLoader();
    GeneratedClassLoader loader;
    loader = SecurityController.createLoader(rhinoLoader, staticSecurityDomain);
    Exception e;
    try {
      Class<?> cl = loader.defineClass(className, classBytes);
      loader.linkClass(cl);
      return cl;
    } catch (SecurityException x) {
      e = x;
    } catch (IllegalArgumentException x) {
      e = x;
    }
    throw new RuntimeException("Malformed optimizer package " + e);
  }

  private void emitConstantDudeInitializers(ClassFileWriter cfw) {
    int N = itsConstantListSize;
    if (N == 0)
      return;

    cfw.startMethod("<clinit>", "()V",
        (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_FINAL));

    double[] array = itsConstantList;
    for (int i = 0; i != N; ++i) {
      double num = array[i];
      String constantName = "_k" + i;
      String constantType = getStaticConstantWrapperType(num);
      cfw.addField(constantName, constantType,
          (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));
      int inum = (int) num;
      if (inum == num) {
        cfw.add(ByteCode.NEW, "java/lang/Integer");
        cfw.add(ByteCode.DUP);
        cfw.addPush(inum);
        cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Integer", "<init>",
            "(I)V");
      } else {
        cfw.addPush(num);
        addDoubleWrap(cfw);
      }
      cfw.add(ByteCode.PUTSTATIC, mainClassName, constantName, constantType);
    }

    cfw.add(ByteCode.RETURN);
    cfw.stopMethod((short) 0);
  }

  private void emitDirectConstructor(ClassFileWriter cfw, OptFunctionNode ofn) {
    /*
     * we generate .. Scriptable directConstruct(<directCallArgs>) { Scriptable
     * newInstance = createObject(cx, scope); Object val = <body-name>(cx,
     * scope, newInstance, <directCallArgs>); if (val instanceof Scriptable) {
     * return (Scriptable) val; } return newInstance; }
     */
    cfw.startMethod(getDirectCtorName(ofn.fnode),
        getBodyMethodSignature(ofn.fnode),
        (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));

    int argCount = ofn.fnode.getParamCount();
    int firstLocal = 4 + argCount * 3 + 1;

    cfw.addALoad(0); // this
    cfw.addALoad(1); // cx
    cfw.addALoad(2); // scope
    cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
        "org/mozilla/javascript/BaseFunction", "createObject",
        "(Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + ")Lorg/mozilla/javascript/Scriptable;");
    cfw.addAStore(firstLocal);

    cfw.addALoad(0);
    cfw.addALoad(1);
    cfw.addALoad(2);
    cfw.addALoad(firstLocal);
    for (int i = 0; i < argCount; i++) {
      cfw.addALoad(4 + i * 3);
      cfw.addDLoad(5 + i * 3);
    }
    cfw.addALoad(4 + argCount * 3);
    cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
        getBodyMethodName(ofn.fnode), getBodyMethodSignature(ofn.fnode));
    int exitLabel = cfw.acquireLabel();
    cfw.add(ByteCode.DUP); // make a copy of direct call result
    cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable");
    cfw.add(ByteCode.IFEQ, exitLabel);
    // cast direct call result
    cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable");
    cfw.add(ByteCode.ARETURN);
    cfw.markLabel(exitLabel);

    cfw.addALoad(firstLocal);
    cfw.add(ByteCode.ARETURN);

    cfw.stopMethod((short) (firstLocal + 1));
  }

  private void emitRegExpInit(ClassFileWriter cfw) {
    // precompile all regexp literals

    int totalRegCount = 0;
    for (int i = 0; i != scriptOrFnNodes.length; ++i)
      totalRegCount += scriptOrFnNodes[i].getRegexpCount();
    if (totalRegCount == 0)
      return;

    cfw
        .startMethod(
            REGEXP_INIT_METHOD_NAME,
            REGEXP_INIT_METHOD_SIGNATURE,
            (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE | ClassFileWriter.ACC_SYNCHRONIZED));
    cfw.addField("_reInitDone", "Z",
        (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));
    cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z");
    int doInit = cfw.acquireLabel();
    cfw.add(ByteCode.IFEQ, doInit);
    cfw.add(ByteCode.RETURN);
    cfw.markLabel(doInit);

    for (int i = 0; i != scriptOrFnNodes.length; ++i) {
      ScriptOrFnNode n = scriptOrFnNodes[i];
      int regCount = n.getRegexpCount();
      for (int j = 0; j != regCount; ++j) {
        String reFieldName = getCompiledRegexpName(n, j);
        String reFieldType = "Ljava/lang/Object;";
        String reString = n.getRegexpString(j);
        String reFlags = n.getRegexpFlags(j);
        cfw.addField(reFieldName, reFieldType,
            (short) (ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE));
        cfw.addALoad(0); // proxy
        cfw.addALoad(1); // context
        cfw.addPush(reString);
        if (reFlags == null)
          cfw.add(ByteCode.ACONST_NULL);
        else
          cfw.addPush(reFlags);
        cfw.addInvoke(ByteCode.INVOKEINTERFACE,
            "org/mozilla/javascript/RegExpProxy", "compileRegExp",
            "(Lorg/mozilla/javascript/Context;"
                + "Ljava/lang/String;Ljava/lang/String;"
                + ")Ljava/lang/Object;");
        cfw.add(ByteCode.PUTSTATIC, mainClassName, reFieldName, reFieldType);
      }
    }

    cfw.addPush(1);
    cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z");
    cfw.add(ByteCode.RETURN);
    cfw.stopMethod((short) 2);
  }

  private void generateCallMethod(ClassFileWriter cfw) {
    cfw.startMethod("call", "(Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + "[Ljava/lang/Object;)Ljava/lang/Object;",
        (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));

    // Generate code for:
    // if (!ScriptRuntime.hasTopCall(cx)) {
    // return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args);
    // }

    int nonTopCallLabel = cfw.acquireLabel();
    cfw.addALoad(1); // cx
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/ScriptRuntime", "hasTopCall",
        "(Lorg/mozilla/javascript/Context;" + ")Z");
    cfw.add(ByteCode.IFNE, nonTopCallLabel);
    cfw.addALoad(0);
    cfw.addALoad(1);
    cfw.addALoad(2);
    cfw.addALoad(3);
    cfw.addALoad(4);
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/ScriptRuntime", "doTopCall",
        "(Lorg/mozilla/javascript/Callable;"
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
            + ")Ljava/lang/Object;");
    cfw.add(ByteCode.ARETURN);
    cfw.markLabel(nonTopCallLabel);

    // Now generate switch to call the real methods
    cfw.addALoad(0);
    cfw.addALoad(1);
    cfw.addALoad(2);
    cfw.addALoad(3);
    cfw.addALoad(4);

    int end = scriptOrFnNodes.length;
    boolean generateSwitch = 2 <= end;

    int switchStart = 0;
    int switchStackTop = 0;
    if (generateSwitch) {
      cfw.addLoadThis();
      cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
      // do switch from (1, end - 1) mapping 0 to
      // the default case
      switchStart = cfw.addTableSwitch(1, end - 1);
    }

    for (int i = 0; i != end; ++i) {
      ScriptOrFnNode n = scriptOrFnNodes[i];
      if (generateSwitch)
        if (i == 0) {
          cfw.markTableSwitchDefault(switchStart);
          switchStackTop = cfw.getStackTop();
        } else
          cfw.markTableSwitchCase(switchStart, i - 1, switchStackTop);
      if (n.getType() == Token.FUNCTION) {
        OptFunctionNode ofn = OptFunctionNode.get(n);
        if (ofn.isTargetOfDirectCall()) {
          int pcount = ofn.fnode.getParamCount();
          if (pcount != 0)
            // loop invariant:
            // stack top == arguments array from addALoad4()
            for (int p = 0; p != pcount; ++p) {
              cfw.add(ByteCode.ARRAYLENGTH);
              cfw.addPush(p);
              int undefArg = cfw.acquireLabel();
              int beyond = cfw.acquireLabel();
              cfw.add(ByteCode.IF_ICMPLE, undefArg);
              // get array[p]
              cfw.addALoad(4);
              cfw.addPush(p);
              cfw.add(ByteCode.AALOAD);
              cfw.add(ByteCode.GOTO, beyond);
              cfw.markLabel(undefArg);
              pushUndefined(cfw);
              cfw.markLabel(beyond);
              // Only one push
              cfw.adjustStackTop(-1);
              cfw.addPush(0.0);
              // restore invariant
              cfw.addALoad(4);
            }
        }
      }
      cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName, getBodyMethodName(n),
          getBodyMethodSignature(n));
      cfw.add(ByteCode.ARETURN);
    }
    cfw.stopMethod((short) 5);
    // 5: this, cx, scope, js this, args[]
  }

  private byte[] generateCode(String encodedSource) {
    boolean hasScript = scriptOrFnNodes[0].getType() == Token.SCRIPT;
    boolean hasFunctions = scriptOrFnNodes.length > 1 || !hasScript;

    String sourceFile = null;
    if (compilerEnv.isGenerateDebugInfo())
      sourceFile = scriptOrFnNodes[0].getSourceName();

    ClassFileWriter cfw = new ClassFileWriter(mainClassName, SUPER_CLASS_NAME,
        sourceFile);
    cfw.addField(ID_FIELD_NAME, "I", ClassFileWriter.ACC_PRIVATE);
    cfw.addField(DIRECT_CALL_PARENT_FIELD, mainClassSignature,
        ClassFileWriter.ACC_PRIVATE);
    cfw.addField(REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE,
        ClassFileWriter.ACC_PRIVATE);

    if (hasFunctions)
      generateFunctionConstructor(cfw);

    if (hasScript) {
      cfw.addInterface("org/mozilla/javascript/Script");
      generateScriptCtor(cfw);
      generateMain(cfw);
      generateExecute(cfw);
    }

    generateCallMethod(cfw);
    generateResumeGenerator(cfw);

    generateNativeFunctionOverrides(cfw, encodedSource);

    int count = scriptOrFnNodes.length;
    for (int i = 0; i != count; ++i) {
      ScriptOrFnNode n = scriptOrFnNodes[i];

      BodyCodegen bodygen = new BodyCodegen();
      bodygen.cfw = cfw;
      bodygen.codegen = this;
      bodygen.compilerEnv = compilerEnv;
      bodygen.scriptOrFn = n;
      bodygen.scriptOrFnIndex = i;

      try {
        bodygen.generateBodyCode();
      } catch (ClassFileWriter.ClassFileFormatException e) {
        throw reportClassFileFormatException(n, e.getMessage());
      }

      if (n.getType() == Token.FUNCTION) {
        OptFunctionNode ofn = OptFunctionNode.get(n);
        generateFunctionInit(cfw, ofn);
        if (ofn.isTargetOfDirectCall())
          emitDirectConstructor(cfw, ofn);
      }
    }

    if (directCallTargets != null) {
      int N = directCallTargets.size();
      for (int j = 0; j != N; ++j)
        cfw.addField(getDirectTargetFieldName(j), mainClassSignature,
            ClassFileWriter.ACC_PRIVATE);
    }

    emitRegExpInit(cfw);
    emitConstantDudeInitializers(cfw);

    return cfw.toByteArray();
  }

  private void generateExecute(ClassFileWriter cfw) {
    cfw.startMethod("exec", "(Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;" + ")Ljava/lang/Object;",
        (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));

    final int CONTEXT_ARG = 1;
    final int SCOPE_ARG = 2;

    cfw.addLoadThis();
    cfw.addALoad(CONTEXT_ARG);
    cfw.addALoad(SCOPE_ARG);
    cfw.add(ByteCode.DUP);
    cfw.add(ByteCode.ACONST_NULL);
    cfw.addInvoke(ByteCode.INVOKEVIRTUAL, cfw.getClassName(), "call",
        "(Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;"
            + "Lorg/mozilla/javascript/Scriptable;" + "[Ljava/lang/Object;"
            + ")Ljava/lang/Object;");

    cfw.add(ByteCode.ARETURN);
    // 3 = this + context + scope
    cfw.stopMethod((short) 3);
  }

  private void generateFunctionConstructor(ClassFileWriter cfw) {
    final int SCOPE_ARG = 1;
    final int CONTEXT_ARG = 2;
    final int ID_ARG = 3;

    cfw.startMethod("<init>", FUNCTION_CONSTRUCTOR_SIGNATURE,
        ClassFileWriter.ACC_PUBLIC);
    cfw.addALoad(0);
    cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, "<init>", "()V");

    cfw.addLoadThis();
    cfw.addILoad(ID_ARG);
    cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

    cfw.addLoadThis();
    cfw.addALoad(CONTEXT_ARG);
    cfw.addALoad(SCOPE_ARG);

    int start = scriptOrFnNodes[0].getType() == Token.SCRIPT ? 1 : 0;
    int end = scriptOrFnNodes.length;
    if (start == end)
      throw badTree();
    boolean generateSwitch = 2 <= end - start;

    int switchStart = 0;
    int switchStackTop = 0;
    if (generateSwitch) {
      cfw.addILoad(ID_ARG);
      // do switch from (start + 1, end - 1) mapping start to
      // the default case
      switchStart = cfw.addTableSwitch(start + 1, end - 1);
    }

    for (int i = start; i != end; ++i) {
      if (generateSwitch)
        if (i == start) {
          cfw.markTableSwitchDefault(switchStart);
          switchStackTop = cfw.getStackTop();
        } else
          cfw.markTableSwitchCase(switchStart, i - 1 - start, switchStackTop);
      OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]);
      cfw.addInvoke(ByteCode.INVOKEVIRTUAL, mainClassName,
          getFunctionInitMethodName(ofn), FUNCTION_INIT_SIGNATURE);
      cfw.add(ByteCode.RETURN);
    }

    // 4 = this + scope + context + id
    cfw.stopMethod((short) 4);
  }

  private void generateFunctionInit(ClassFileWriter cfw, OptFunctionNode ofn) {
    final int CONTEXT_ARG = 1;
    final int SCOPE_ARG = 2;
    cfw.startMethod(getFunctionInitMethodName(ofn), FUNCTION_INIT_SIGNATURE,
        (short) (ClassFileWriter.ACC_PRIVATE | ClassFileWriter.ACC_FINAL));

    // Call NativeFunction.initScriptFunction
    cfw.addLoadThis();
    cfw.addALoad(CONTEXT_ARG);
    cfw.addALoad(SCOPE_ARG);
    cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
        "org/mozilla/javascript/NativeFunction", "initScriptFunction",
        "(Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + ")V");

    // precompile all regexp literals
    int regexpCount = ofn.fnode.getRegexpCount();
    if (regexpCount != 0) {
      cfw.addLoadThis();
      pushRegExpArray(cfw, ofn.fnode, CONTEXT_ARG, SCOPE_ARG);
      cfw.add(ByteCode.PUTFIELD, mainClassName, REGEXP_ARRAY_FIELD_NAME,
          REGEXP_ARRAY_FIELD_TYPE);
    }

    cfw.add(ByteCode.RETURN);
    // 3 = (scriptThis/functionRef) + scope + context
    cfw.stopMethod((short) 3);
  }

  private void generateMain(ClassFileWriter cfw) {
    cfw.startMethod("main", "([Ljava/lang/String;)V",
        (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_STATIC));

    // load new ScriptImpl()
    cfw.add(ByteCode.NEW, cfw.getClassName());
    cfw.add(ByteCode.DUP);
    cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(), "<init>", "()V");
    // load 'args'
    cfw.add(ByteCode.ALOAD_0);
    // Call mainMethodClass.main(Script script, String[] args)
    cfw.addInvoke(ByteCode.INVOKESTATIC, mainMethodClass, "main",
        "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V");
    cfw.add(ByteCode.RETURN);
    // 1 = String[] args
    cfw.stopMethod((short) 1);
  }

  private void generateNativeFunctionOverrides(ClassFileWriter cfw,
      String encodedSource) {
    // Override NativeFunction.getLanguageVersion() with
    // public int getLanguageVersion() { return <version-constant>; }

    cfw.startMethod("getLanguageVersion", "()I", ClassFileWriter.ACC_PUBLIC);

    cfw.addPush(compilerEnv.getLanguageVersion());
    cfw.add(ByteCode.IRETURN);

    // 1: this and no argument or locals
    cfw.stopMethod((short) 1);

    // The rest of NativeFunction overrides require specific code for each
    // script/function id

    final int Do_getFunctionName = 0;
    final int Do_getParamCount = 1;
    final int Do_getParamAndVarCount = 2;
    final int Do_getParamOrVarName = 3;
    final int Do_getEncodedSource = 4;
    final int Do_getParamOrVarConst = 5;
    final int SWITCH_COUNT = 6;

    for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
      if (methodIndex == Do_getEncodedSource && encodedSource == null)
        continue;

      // Generate:
      // prologue;
      // switch over function id to implement function-specific action
      // epilogue

      short methodLocals;
      switch (methodIndex) {
        case Do_getFunctionName :
          methodLocals = 1; // Only this
          cfw.startMethod("getFunctionName", "()Ljava/lang/String;",
              ClassFileWriter.ACC_PUBLIC);
          break;
        case Do_getParamCount :
          methodLocals = 1; // Only this
          cfw.startMethod("getParamCount", "()I", ClassFileWriter.ACC_PUBLIC);
          break;
        case Do_getParamAndVarCount :
          methodLocals = 1; // Only this
          cfw.startMethod("getParamAndVarCount", "()I",
              ClassFileWriter.ACC_PUBLIC);
          break;
        case Do_getParamOrVarName :
          methodLocals = 1 + 1; // this + paramOrVarIndex
          cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;",
              ClassFileWriter.ACC_PUBLIC);
          break;
        case Do_getParamOrVarConst :
          methodLocals = 1 + 1 + 1; // this + paramOrVarName
          cfw.startMethod("getParamOrVarConst", "(I)Z",
              ClassFileWriter.ACC_PUBLIC);
          break;
        case Do_getEncodedSource :
          methodLocals = 1; // Only this
          cfw.startMethod("getEncodedSource", "()Ljava/lang/String;",
              ClassFileWriter.ACC_PUBLIC);
          cfw.addPush(encodedSource);
          break;
        default :
          throw Kit.codeBug();
      }

      int count = scriptOrFnNodes.length;

      int switchStart = 0;
      int switchStackTop = 0;
      if (count > 1) {
        // Generate switch but only if there is more then one
        // script/function
        cfw.addLoadThis();
        cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

        // do switch from 1 .. count - 1 mapping 0 to the default case
        switchStart = cfw.addTableSwitch(1, count - 1);
      }

      for (int i = 0; i != count; ++i) {
        ScriptOrFnNode n = scriptOrFnNodes[i];
        if (i == 0) {
          if (count > 1) {
            cfw.markTableSwitchDefault(switchStart);
            switchStackTop = cfw.getStackTop();
          }
        } else
          cfw.markTableSwitchCase(switchStart, i - 1, switchStackTop);

        // Impelemnet method-specific switch code
        switch (methodIndex) {
          case Do_getFunctionName :
            // Push function name
            if (n.getType() == Token.SCRIPT)
              cfw.addPush("");
            else {
              String name = ((FunctionNode) n).getFunctionName();
              cfw.addPush(name);
            }
            cfw.add(ByteCode.ARETURN);
            break;

          case Do_getParamCount :
            // Push number of defined parameters
            cfw.addPush(n.getParamCount());
            cfw.add(ByteCode.IRETURN);
            break;

          case Do_getParamAndVarCount :
            // Push number of defined parameters and declared variables
            cfw.addPush(n.getParamAndVarCount());
            cfw.add(ByteCode.IRETURN);
            break;

          case Do_getParamOrVarName :
            // Push name of parameter using another switch
            // over paramAndVarCount
            int paramAndVarCount = n.getParamAndVarCount();
            if (paramAndVarCount == 0) {
              // The runtime should never call the method in this
              // case but to make bytecode verifier happy return null
              // as throwing execption takes more code
              cfw.add(ByteCode.ACONST_NULL);
              cfw.add(ByteCode.ARETURN);
            } else if (paramAndVarCount == 1) {
              // As above do not check for valid index but always
              // return the name of the first param
              cfw.addPush(n.getParamOrVarName(0));
              cfw.add(ByteCode.ARETURN);
            } else {
              // Do switch over getParamOrVarName
              cfw.addILoad(1); // param or var index
              // do switch from 1 .. paramAndVarCount - 1 mapping 0
              // to the default case
              int paramSwitchStart = cfw
                  .addTableSwitch(1, paramAndVarCount - 1);
              for (int j = 0; j != paramAndVarCount; ++j) {
                if (cfw.getStackTop() != 0)
                  Kit.codeBug();
                String s = n.getParamOrVarName(j);
                if (j == 0)
                  cfw.markTableSwitchDefault(paramSwitchStart);
                else
                  cfw.markTableSwitchCase(paramSwitchStart, j - 1, 0);
                cfw.addPush(s);
                cfw.add(ByteCode.ARETURN);
              }
            }
            break;

          case Do_getParamOrVarConst :
            // Push name of parameter using another switch
            // over paramAndVarCount
            paramAndVarCount = n.getParamAndVarCount();
            boolean[] constness = n.getParamAndVarConst();
            if (paramAndVarCount == 0) {
              // The runtime should never call the method in this
              // case but to make bytecode verifier happy return null
              // as throwing execption takes more code
              cfw.add(ByteCode.ICONST_0);
              cfw.add(ByteCode.IRETURN);
            } else if (paramAndVarCount == 1) {
              // As above do not check for valid index but always
              // return the name of the first param
              cfw.addPush(constness[0]);
              cfw.add(ByteCode.IRETURN);
            } else {
              // Do switch over getParamOrVarName
              cfw.addILoad(1); // param or var index
              // do switch from 1 .. paramAndVarCount - 1 mapping 0
              // to the default case
              int paramSwitchStart = cfw
                  .addTableSwitch(1, paramAndVarCount - 1);
              for (int j = 0; j != paramAndVarCount; ++j) {
                if (cfw.getStackTop() != 0)
                  Kit.codeBug();
                if (j == 0)
                  cfw.markTableSwitchDefault(paramSwitchStart);
                else
                  cfw.markTableSwitchCase(paramSwitchStart, j - 1, 0);
                cfw.addPush(constness[j]);
                cfw.add(ByteCode.IRETURN);
              }
            }
            break;

          case Do_getEncodedSource :
            // Push number encoded source start and end
            // to prepare for encodedSource.substring(start, end)
            cfw.addPush(n.getEncodedSourceStart());
            cfw.addPush(n.getEncodedSourceEnd());
            cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String",
                "substring", "(II)Ljava/lang/String;");
            cfw.add(ByteCode.ARETURN);
            break;

          default :
            throw Kit.codeBug();
        }
      }

      cfw.stopMethod(methodLocals);
    }
  }
  // How dispatch to generators works:
  // Two methods are generated corresponding to a user-written generator.
  // One of these creates a generator object (NativeGenerator), which is
  // returned to the user. The other method contains all of the body code
  // of the generator.
  // When a user calls a generator, the call() method dispatches control to
  // to the method that creates the NativeGenerator object. Subsequently when
  // the user invokes .next(), .send() or any such method on the generator
  // object, the resumeGenerator() below dispatches the call to the
  // method corresponding to the generator body. As a matter of convention
  // the generator body is given the name of the generator activation function
  // appended by "_gen".
  private void generateResumeGenerator(ClassFileWriter cfw) {
    boolean hasGenerators = false;
    for (ScriptOrFnNode scriptOrFnNode : scriptOrFnNodes)
      if (isGenerator(scriptOrFnNode))
        hasGenerators = true;

    // if there are no generators defined, we don't implement a
    // resumeGenerator(). The base class provides a default implementation.
    if (!hasGenerators)
      return;

    cfw.startMethod("resumeGenerator", "(Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;" + "ILjava/lang/Object;"
        + "Ljava/lang/Object;)Ljava/lang/Object;",
        (short) (ClassFileWriter.ACC_PUBLIC | ClassFileWriter.ACC_FINAL));

    // load arguments for dispatch to the corresponding *_gen method
    cfw.addALoad(0);
    cfw.addALoad(1);
    cfw.addALoad(2);
    cfw.addALoad(4);
    cfw.addALoad(5);
    cfw.addILoad(3);

    cfw.addLoadThis();
    cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

    int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1);
    cfw.markTableSwitchDefault(startSwitch);
    int endlabel = cfw.acquireLabel();

    for (int i = 0; i < scriptOrFnNodes.length; i++) {
      ScriptOrFnNode n = scriptOrFnNodes[i];
      cfw.markTableSwitchCase(startSwitch, i, (short) 6);
      if (isGenerator(n)) {
        String type = "(" + mainClassSignature
            + "Lorg/mozilla/javascript/Context;"
            + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;"
            + "Ljava/lang/Object;I)Ljava/lang/Object;";
        cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
            getBodyMethodName(n) + "_gen", type);
        cfw.add(ByteCode.ARETURN);
      } else
        cfw.add(ByteCode.GOTO, endlabel);
    }

    cfw.markLabel(endlabel);
    pushUndefined(cfw);
    cfw.add(ByteCode.ARETURN);

    // this method uses as many locals as there are arguments (hence 6)
    cfw.stopMethod((short) 6);
  }

  private void generateScriptCtor(ClassFileWriter cfw) {
    cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);

    cfw.addLoadThis();
    cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, "<init>", "()V");
    // set id to 0
    cfw.addLoadThis();
    cfw.addPush(0);
    cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

    cfw.add(ByteCode.RETURN);
    // 1 parameter = this
    cfw.stopMethod((short) 1);
  }
  String getBodyMethodName(ScriptOrFnNode n) {
    return "_c" + getIndex(n);
  }
  String getBodyMethodSignature(ScriptOrFnNode n) {
    StringBuffer sb = new StringBuffer();
    sb.append('(');
    sb.append(mainClassSignature);
    sb.append("Lorg/mozilla/javascript/Context;"
        + "Lorg/mozilla/javascript/Scriptable;"
        + "Lorg/mozilla/javascript/Scriptable;");
    if (n.getType() == Token.FUNCTION) {
      OptFunctionNode ofn = OptFunctionNode.get(n);
      if (ofn.isTargetOfDirectCall()) {
        int pCount = ofn.fnode.getParamCount();
        for (int i = 0; i != pCount; i++)
          sb.append("Ljava/lang/Object;D");
      }
    }
    sb.append("[Ljava/lang/Object;)Ljava/lang/Object;");
    return sb.toString();
  }
  String getCompiledRegexpName(ScriptOrFnNode n, int regexpIndex) {
    return "_re" + getIndex(n) + "_" + regexpIndex;
  }

  String getDirectCtorName(ScriptOrFnNode n) {
    return "_n" + getIndex(n);
  }

  String getFunctionInitMethodName(OptFunctionNode ofn) {
    return "_i" + getIndex(ofn.fnode);
  }

  int getIndex(ScriptOrFnNode n) {
    return scriptOrFnIndexes.getExisting(n);
  }
  public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
    throw new UnsupportedOperationException();
  }

  public List<String> getScriptStack(RhinoException ex) {
    throw new UnsupportedOperationException();
  }

  public String getSourcePositionFromStack(Context cx, int[] linep) {
    throw new UnsupportedOperationException();
  }
  private void initScriptOrFnNodesData(ScriptOrFnNode scriptOrFn) {
    ObjArray x = new ObjArray();
    collectScriptOrFnNodes_r(scriptOrFn, x);

    int count = x.size();
    scriptOrFnNodes = new ScriptOrFnNode[count];
    x.toArray(scriptOrFnNodes);

    scriptOrFnIndexes = new ObjToIntMap(count);
    for (int i = 0; i != count; ++i)
      scriptOrFnIndexes.put(scriptOrFnNodes[i], i);
  }
  void pushNumberAsObject(ClassFileWriter cfw, double num) {
    if (num == 0.0) {
      if (1 / num > 0)
        // +0.0
        cfw.add(ByteCode.GETSTATIC,
            "org/mozilla/javascript/optimizer/OptRuntime", "zeroObj",
            "Ljava/lang/Double;");
      else {
        cfw.addPush(num);
        addDoubleWrap(cfw);
      }

    } else if (num == 1.0) {
      cfw.add(ByteCode.GETSTATIC,
          "org/mozilla/javascript/optimizer/OptRuntime", "oneObj",
          "Ljava/lang/Double;");
      return;

    } else if (num == -1.0)
      cfw.add(ByteCode.GETSTATIC,
          "org/mozilla/javascript/optimizer/OptRuntime", "minusOneObj",
          "Ljava/lang/Double;");
    else if (num != num)
      cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/ScriptRuntime",
          "NaNobj", "Ljava/lang/Double;");
    else if (itsConstantListSize >= 2000) {
      // There appears to be a limit in the JVM on either the number
      // of static fields in a class or the size of the class
      // initializer. Either way, we can't have any more than 2000
      // statically init'd constants.
      cfw.addPush(num);
      addDoubleWrap(cfw);

    } else {
      int N = itsConstantListSize;
      int index = 0;
      if (N == 0)
        itsConstantList = new double[64];
      else {
        double[] array = itsConstantList;
        while (index != N && array[index] != num)
          ++index;
        if (N == array.length) {
          array = new double[N * 2];
          System.arraycopy(itsConstantList, 0, array, 0, N);
          itsConstantList = array;
        }
      }
      if (index == N) {
        itsConstantList[N] = num;
        itsConstantListSize = N + 1;
      }
      String constantName = "_k" + index;
      String constantType = getStaticConstantWrapperType(num);
      cfw.add(ByteCode.GETSTATIC, mainClassName, constantName, constantType);
    }
  }

  void pushRegExpArray(ClassFileWriter cfw, ScriptOrFnNode n, int contextArg,
      int scopeArg) {
    int regexpCount = n.getRegexpCount();
    if (regexpCount == 0)
      throw badTree();

    cfw.addPush(regexpCount);
    cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");

    cfw.addALoad(contextArg);
    cfw.addInvoke(ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/ScriptRuntime", "checkRegExpProxy",
        "(Lorg/mozilla/javascript/Context;"
            + ")Lorg/mozilla/javascript/RegExpProxy;");
    // Stack: proxy, array
    cfw.add(ByteCode.DUP);
    cfw.addALoad(contextArg);
    cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
        REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE);
    for (int i = 0; i != regexpCount; ++i) {
      // Stack: proxy, array
      cfw.add(ByteCode.DUP2);
      cfw.addALoad(contextArg);
      cfw.addALoad(scopeArg);
      cfw.add(ByteCode.GETSTATIC, mainClassName, getCompiledRegexpName(n, i),
          "Ljava/lang/Object;");
      // Stack: compiledRegExp, scope, cx, proxy, array, proxy, array
      cfw.addInvoke(ByteCode.INVOKEINTERFACE,
          "org/mozilla/javascript/RegExpProxy", "wrapRegExp",
          "(Lorg/mozilla/javascript/Context;"
              + "Lorg/mozilla/javascript/Scriptable;" + "Ljava/lang/Object;"
              + ")Lorg/mozilla/javascript/Scriptable;");
      // Stack: wrappedRegExp, array, proxy, array
      cfw.addPush(i);
      cfw.add(ByteCode.SWAP);
      cfw.add(ByteCode.AASTORE);
      // Stack: proxy, array
    }
    // remove proxy
    cfw.add(ByteCode.POP);
  }

  private RuntimeException reportClassFileFormatException(
      ScriptOrFnNode scriptOrFn, String message) {
    String msg = scriptOrFn instanceof FunctionNode ? ScriptRuntime
        .getMessage2("msg.while.compiling.fn", ((FunctionNode) scriptOrFn)
            .getFunctionName(), message) : ScriptRuntime.getMessage1(
        "msg.while.compiling.script", message);
    return Context.reportRuntimeError(msg, scriptOrFn.getSourceName(),
        scriptOrFn.getLineno(), null, 0);
  }
  public void setEvalScriptFlag(Script script) {
    throw new UnsupportedOperationException();
  }

  void setMainMethodClass(String className) {
    mainMethodClass = className;
  }
  private void transform(ScriptOrFnNode tree) {
    initOptFunctions_r(tree);

    int optLevel = compilerEnv.getOptimizationLevel();

    Map<String, OptFunctionNode> possibleDirectCalls = null;
    if (optLevel > 0)
      /*
       * Collect all of the contained functions into a hashtable so that the
       * call optimizer can access the class name & parameter count for any call
       * it encounters
       */
      if (tree.getType() == Token.SCRIPT) {
        int functionCount = tree.getFunctionCount();
        for (int i = 0; i != functionCount; ++i) {
          OptFunctionNode ofn = OptFunctionNode.get(tree, i);
          if (ofn.fnode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT) {
            String name = ofn.fnode.getFunctionName();
            if (name.length() != 0) {
              if (possibleDirectCalls == null)
                possibleDirectCalls = new HashMap<String, OptFunctionNode>();
              possibleDirectCalls.put(name, ofn);
            }
          }
        }
      }

    if (possibleDirectCalls != null)
      directCallTargets = new ObjArray();

    OptTransformer ot = new OptTransformer(possibleDirectCalls,
        directCallTargets);
    ot.transform(tree);

    if (optLevel > 0)
      new Optimizer().optimize(tree);
  }
}
